feat(ui): align workbench with Stitch screens
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-19 16:38:17 +02:00
parent 87345f1a8c
commit 1b9848a5f4
10 changed files with 883 additions and 195 deletions
+8
View File
@@ -13,6 +13,14 @@
Atlas Workbench. The new shell uses one compact icon navigation rail, one
context bar and task-specific work surfaces across Map, Sources, AI
Questions, Quality, Image Analysis, Downloads, Status and Administration.
- Completed a screen-by-screen Stitch parity pass with dedicated designs for
Quality, Image Analysis, Downloads and Status/Administration. Sources,
Quality, Image Analysis and Downloads now use the same master-detail and
inspector structures as their Stitch references instead of generic panel
stacks.
- Bundled Public Sans and Manrope locally and aligned the implementation with
the exact Stitch canvas, context, ink, border, teal, steel and amber tokens.
No API, persistence, GIS or AI behavior changed.
- Restored the normal desktop map to one stable theme/map/insight row, made
the map absorb ultrawide space, bounded the mobile theme list and removed
the duplicate workspace command bar. API, database, GIS and AI behavior are
+48
View File
@@ -10861,3 +10861,51 @@ Behavior preservation:
calculation, model runtime or persistence flow changed.
- Existing map selection, source loading, local Ollama, QA, analysis and
download actions remain wired through their original hooks and services.
## 2026-07-19 - Stitch screen parity and responsive refinement
Implemented:
- Extended the Stitch project with dedicated Quality, Image Analysis,
Downloads and Status/Administration screens, all using design-system asset
`4667932515738184526`.
- Replaced the residual dark navigation treatment with the white 88-pixel
Stitch rail, pale context bar and locally bundled Public Sans/Manrope
typography.
- Made Sources a deterministic three-pane workspace after removing a legacy
full-width catalogue override.
- Added the Stitch control/result/inspector structure to the empty Quality
state and kept the existing populated evidence/history implementation.
- Compacted detection model administration into one row above the existing
configuration/result/QA workbench.
- Reorganized Downloads as artifact actions, current/history results and a
preview inspector without changing export callbacks or formats.
- Kept the MapLibre map full-height with icon controls and stable rails on
normal and ultrawide displays.
Visual verification:
- compared Map, Sources, AI Questions, Quality, Image Analysis, Downloads,
Status and Administration against their dedicated Stitch references;
- reviewed live-backend states at 1280x720, 1920x1080 and 390x844;
- confirmed that the widescreen map absorbs the additional width and that the
mobile workspaces remain vertically navigable without document overflow.
Validation:
- the complete readiness gate passed 1,052 backend tests, 22 frontend tests,
frontend typecheck/build and Alembic head `202607160001`;
- the final UX browser audit passed at 390x844, 1366x768 and 2560x1080 with
zero horizontal overflow, console errors or failed requests;
- all seven Belgium/North Sea release journeys passed through the redesigned
local frontend against the live backend;
- the journey retry caught and verified a fixed Sources grid containment issue
that previously allowed the Area panel to intercept a scrolled Project
management control.
Behavior preservation:
- no backend, API contract, migration, persistence, GIS metric, provider,
Ollama or model-runtime behavior changed;
- all existing actions continue through the original React hooks and service
layer.
+24 -6
View File
@@ -15,9 +15,15 @@ The redesign was developed in Stitch under:
- Map Explorer screen: `c24113d1f1474c91b332b2365be24993`
- Sources screen: `239796f2d66a44e7983e5fc588305ff8`
- AI Questions screen: `246aa57bbe974861885325ff9d10c6bd`
- Quality screen: `06b4b69387d847a0bba33c6953563860`
- Image Analysis screen: `200a196f4e524c979167e40905ae87f2`
- Downloads screen: `f6d91a48e98141bb94f680f626078a1e`
- Status and Administration screen: `3d685f83c61d4e9687bdfdf608220b26`
The React implementation remains the source of truth for behavior. Stitch is
the design reference for hierarchy, spacing and component treatment.
the design reference for hierarchy, spacing and component treatment. Each
primary workspace has a dedicated Stitch screen; new layout work must compare
against the corresponding screen rather than only borrowing the color palette.
## Product hierarchy
@@ -37,6 +43,12 @@ command bars or competing shortcut rows must not be reintroduced.
- Desktop uses an 88-pixel navigation rail and a 56-pixel context bar.
- The normal map layout is `theme drawer / map / insight inspector`.
- Sources uses `workspace and area / source inventory / loaded datasets`.
- AI Questions uses `area context / conversation and composer`.
- Quality uses `controls / result history / evidence inspector`.
- Image Analysis uses `configuration / persisted results / QA`.
- Downloads uses `artifact actions / export history / preview inspector`.
- Administration uses `source category / provider status and provenance`.
- Theme and insight rails stay stable on ultrawide displays; the map receives
the additional width.
- Non-map workspaces use one task header and one primary work surface.
@@ -50,17 +62,21 @@ command bars or competing shortcut rows must not be reintroduced.
| Role | Value |
| --- | --- |
| Canvas | `#f3f6f5` |
| Canvas | `#f5f7f6` |
| Surface | `#ffffff` |
| Ink | `#152522` |
| Muted text | `#687773` |
| Border | `#dce4e1` |
| Pale context | `#eef8f5` |
| Ink | `#162622` |
| Muted text | `#64726f` |
| Border | `#e1e7e5` |
| Primary teal | `#0b6b62` |
| Context blue | `#315d73` |
| Attention amber | `#a55c23` |
| Attention amber | `#b46432` |
| Error red | `#a33c39` |
| Radius | `4px` |
Body and labels use locally bundled Public Sans. Headings use locally bundled
Manrope. The interface must not depend on a remote font request.
The UI does not use gradients, decorative blobs, oversized hero sections,
nested cards or pill-heavy status presentation.
@@ -84,6 +100,8 @@ nested cards or pill-heavy status presentation.
`frontend/src/styles/atlas-workbench.css`
- Map workflow:
`frontend/src/components/map/MapWorkspace.tsx`
- Empty and populated quality review:
`frontend/src/components/quality/QualityResultsPanel.tsx`
- API, persistence and GIS behavior remain outside the design system.
Any future visual change must retain the browser acceptance widths of
+10
View File
@@ -127,6 +127,16 @@ The visual contract, Stitch project and screen references are documented in
hierarchy only; API, persistence and GIS behavior remain owned by the existing
hooks and services.
All eight user-facing destinations now use the Stitch Atlas composition
directly. Map is a flush theme/canvas/insight surface; Sources is a
workspace/catalog/dataset master-detail view; AI Questions keeps context beside
the conversation; Quality keeps controls, results and evidence in three stable
panes; Image Analysis keeps run configuration, persisted results and QA aligned;
Downloads separates artifact actions, history and preview; Status and
Administration use compact operational rows. Public Sans and Manrope are
bundled with the frontend, so typography remains identical without a remote
font dependency.
## Current component boundaries
`App.tsx` remains the shared workspace orchestrator, while focused surfaces
+20
View File
@@ -8,6 +8,8 @@
"name": "geointel-frontend",
"version": "1.0.0",
"dependencies": {
"@fontsource/manrope": "^5.3.0",
"@fontsource/public-sans": "^5.3.0",
"lucide-react": "^1.25.0",
"maplibre-gl": "^4.7.1",
"react": "^18.2.0",
@@ -898,6 +900,24 @@
"node": ">=18"
}
},
"node_modules/@fontsource/manrope": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@fontsource/manrope/-/manrope-5.3.0.tgz",
"integrity": "sha512-obJ1Dv3+uCA6HlHgW8u4BGYxJR9In2HW7gjJhlflEvkrj1X1iSEwu0fToL+JYGC/FEKFfIz1sBuPduvcL2gIAA==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource/public-sans": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@fontsource/public-sans/-/public-sans-5.3.0.tgz",
"integrity": "sha512-kjODI0S3zdv0mBYCIQ8TbBayaiqszpc2UbhJiO3bjIqVVXzcWfHSt2o3WBCLOY3juaGaQoy4MoWCcgmfI5hCuA==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+2
View File
@@ -16,6 +16,8 @@
"node": "^20.19.0 || >=22.12.0"
},
"dependencies": {
"@fontsource/manrope": "^5.3.0",
"@fontsource/public-sans": "^5.3.0",
"lucide-react": "^1.25.0",
"maplibre-gl": "^4.7.1",
"react": "^18.2.0",
+6
View File
@@ -1,4 +1,10 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import '@fontsource/manrope/latin-500.css'
import '@fontsource/manrope/latin-600.css'
import '@fontsource/manrope/latin-700.css'
import '@fontsource/public-sans/latin-400.css'
import '@fontsource/public-sans/latin-500.css'
import '@fontsource/public-sans/latin-600.css'
import './styles/app.css'
import './styles/premium.css'
import './styles/atlas-workbench.css'
+8 -1
View File
@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState, type KeyboardEvent } from 'react'
import { BoxSelect, MapPinned, SlidersHorizontal, Trash2 } from 'lucide-react'
import GeoMap from '../GeoMap'
import type { AreaRead, CoverageResolveResponse, CoverageStatus, DatasetCreateResponse, DetectionQaResult, MapResultExportRequest, MapViewportState, OrthophotoAcquisitionResult, OrthophotoProductRead, ProjectRead, QaComparisonResult, VectorSelectionBBox, VectorSelectionMetric, VectorSelectionResponse } from '../../types'
import { useMapThemeSelectionInsights, type MapThemeAcquisition, type MapThemeQuery } from '../../hooks/useMapThemeSelectionInsights'
@@ -1905,8 +1906,11 @@ export function MapWorkspace({
onClick={() => setAdvancedMode(true)}
aria-expanded={advancedMode}
aria-controls="geo-advanced-workbench"
aria-label="Geavanceerde werkbank"
title="Geavanceerde werkbank"
>
Geavanceerde werkbank
<SlidersHorizontal aria-hidden="true" />
<span>Geavanceerde werkbank</span>
</button>
</header>
@@ -2210,6 +2214,7 @@ export function MapWorkspace({
type="button"
onClick={startBboxSelection}
>
<BoxSelect aria-hidden="true" />
{bboxSelectionMode ? 'Teken op de kaart…' : 'Teken rechthoek'}
</button>
<button
@@ -2223,9 +2228,11 @@ export function MapWorkspace({
}
onClick={() => selectedAreaBbox && void analyzeSelection(selectedAreaBbox, selectedMapArea?.id)}
>
<MapPinned aria-hidden="true" />
{regionalRasterThemeActive || regionalOnDemandThemeActive ? 'Selecteer een deelgebied' : 'Volledig werkgebied'}
</button>
<button className="secondary-action" disabled={!mapSelectionBbox} type="button" onClick={clearAreaSelection}>
<Trash2 aria-hidden="true" />
Wis selectie
</button>
</div>
@@ -208,14 +208,48 @@ export function QualityResultsPanel({
</div>
{qualityChecks.length === 0 ? (
<div className="result-state result-state-empty quality-user-empty-state">
<strong>Nog geen kwaliteitscontrole uitgevoerd</strong>
<p>Selecteer een gebied op de kaart en vergelijk een bewaard resultaat met een officiële referentielaag.</p>
<div className="quality-empty-workbench" aria-label="Lege kwaliteitswerkruimte">
<aside className="quality-empty-context">
<span className="eyebrow">Controles</span>
<strong>Nog geen resultaten</strong>
<p>Kwaliteitscontroles verschijnen hier nadat een resultaat met een officiële referentielaag is vergeleken.</p>
<div className="quality-empty-context-stat">
<span>Te controleren lagen</span>
<strong>{candidateDatasets.length}</strong>
</div>
<div className="quality-empty-context-stat">
<span>Referentielagen</span>
<strong>{referenceDatasets.length}</strong>
</div>
<button type="button" className="secondary-action" onClick={onRefresh} disabled={!selectedProjectId}>
Resultaten vernieuwen
</button>
</aside>
<div className="quality-empty-results">
<div className="quality-empty-column-heading">
<strong>Resultaten</strong>
<span>0 controles</span>
</div>
<div className="result-state result-state-empty quality-user-empty-state">
<strong>Nog geen kwaliteitscontrole uitgevoerd</strong>
<p>Selecteer een gebied op de kaart en vergelijk een bewaard resultaat met een officiële referentielaag.</p>
</div>
</div>
<aside className="quality-empty-inspector">
<div className="quality-empty-column-heading">
<strong>Controle-inspector</strong>
<span>Geen selectie</span>
</div>
<div className="result-state result-state-empty">
<strong>Geen bewijs geselecteerd</strong>
<p>Scores, databronnen en kaartbewijs worden hier samen getoond zodra een controle beschikbaar is.</p>
</div>
</aside>
</div>
) : null}
{qualityChecks.length > 0 ? (
<details className="quality-advanced-disclosure">
<details className="quality-advanced-disclosure" open>
<summary>
<span>Details, kaartbewijs en historiek</span>
<strong>{qualityChecks.length} bewaarde controles</strong>
File diff suppressed because it is too large Load Diff