From a3df75373233490edb7306748ea9f45724866281 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 17 Jun 2026 20:57:43 +0200 Subject: [PATCH] Polish workbench workspace usability --- CHANGELOG.md | 9 + backend/tests/test_sprint19_map_workbench.py | 2 +- .../tests/test_sprint20_area_map_overlay.py | 2 +- .../test_sprint28_dataset_workflow_hook.py | 2 +- .../test_sprint30_workbench_components.py | 7 +- ...est_sprint50_workspace_usability_polish.py | 52 +++++ docs/CODEX_EXECUTION_LOG.md | 24 ++ .../src/components/datasets/DatasetPanel.tsx | 146 +++++++----- .../src/components/detection/DetectionLab.tsx | 201 +++++++++------- frontend/src/components/map/MapWorkspace.tsx | 77 ++++--- frontend/src/components/project/AreaPanel.tsx | 65 ++++-- .../src/components/project/ProjectPanel.tsx | 61 +++-- .../segmentation/SegmentationLab.tsx | 185 +++++++++------ frontend/src/styles/app.css | 214 ++++++++++++++++++ 14 files changed, 751 insertions(+), 296 deletions(-) create mode 100644 backend/tests/test_sprint50_workspace_usability_polish.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bccff83..fccf90f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,15 @@ - Kept existing hooks, API contracts, backend behavior, migrations, provider behavior and AI configuration unchanged. - Added static regression coverage for the new shell regions and workspace navigation anchors. +## Sprint 50 Workspace usability polish (2026-06-17) + +- Reworked the Data workspace panels into compact operator forms and scan-friendly project/AOI/dataset cards. +- Reworked the Map workspace controls into a layer toolbar with clearer AOI/layer status. +- Reworked Detection Lab and Segmentation Lab into model, run, result and QA blocks instead of raw stacked controls. +- Added responsive card/form styling so nested workspaces do not overflow inside the shell. +- Added static regression coverage for the polished workspace structure. +- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed. + ## Sprint 48 Backend API contract audit (2026-06-17) - Added `scripts/audit_api_contracts.py` to compare the active FastAPI route surface with `docs/API_CONTRACTS.md`. diff --git a/backend/tests/test_sprint19_map_workbench.py b/backend/tests/test_sprint19_map_workbench.py index 54b2b0d1..062e4189 100644 --- a/backend/tests/test_sprint19_map_workbench.py +++ b/backend/tests/test_sprint19_map_workbench.py @@ -28,7 +28,7 @@ def test_app_wires_map_workbench_component() -> None: assert "selectedMapFeature" in app assert " None: assert "areaFeatureCollection" in app assert "areaFeatureCollection={areaFeatureCollection}" in app assert "areaData={areaFeatureCollection}" in map_workspace - assert "Area visible" in map_workspace + assert "AOI" in map_workspace assert "area-fill" in geomap assert "area-line" in geomap assert "onSelectMapArea" in area_panel diff --git a/backend/tests/test_sprint28_dataset_workflow_hook.py b/backend/tests/test_sprint28_dataset_workflow_hook.py index c10e4762..0823f3da 100644 --- a/backend/tests/test_sprint28_dataset_workflow_hook.py +++ b/backend/tests/test_sprint28_dataset_workflow_hook.py @@ -65,7 +65,7 @@ def test_app_still_wires_dataset_ui_callbacks() -> None: assert "onRunVectorClip={runVectorClip}" in app assert "onRunVectorBuffer={runVectorBuffer}" in app assert "onRunVectorIntersect={() => runVectorIntersect(availableVectorTargets)}" in app - assert "
onLoadDatasetDetails(selectedProjectId ?? '', dataset)}" in dataset_panel assert "onClick={() => onRefreshMetadata(dataset.id)}" in dataset_panel assert "onRunRasterInspect={onRunRasterInspect}" in detail_panel diff --git a/backend/tests/test_sprint30_workbench_components.py b/backend/tests/test_sprint30_workbench_components.py index 1d059688..921528ca 100644 --- a/backend/tests/test_sprint30_workbench_components.py +++ b/backend/tests/test_sprint30_workbench_components.py @@ -51,9 +51,10 @@ def test_map_workspace_owns_map_controls_and_feature_inspector_markup() -> None: ).read_text(encoding="utf-8") assert "GeoMap" in map_workspace - assert "Selected area" in map_workspace - assert "Area visible" in map_workspace - assert "Layer visible" in map_workspace + assert "map-toolbar" in map_workspace + assert "Area" in map_workspace + assert "AOI" in map_workspace + assert "Active layer" in map_workspace assert "Feature inspector" in map_workspace assert "onFeatureSelect={onSelectMapFeature}" in map_workspace assert "fetch(" not in map_workspace diff --git a/backend/tests/test_sprint50_workspace_usability_polish.py b/backend/tests/test_sprint50_workspace_usability_polish.py new file mode 100644 index 00000000..042b08f5 --- /dev/null +++ b/backend/tests/test_sprint50_workspace_usability_polish.py @@ -0,0 +1,52 @@ +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_data_workspace_panels_use_operator_friendly_cards_and_forms() -> None: + project_panel = (ROOT / "frontend" / "src" / "components" / "project" / "ProjectPanel.tsx").read_text( + encoding="utf-8" + ) + area_panel = (ROOT / "frontend" / "src" / "components" / "project" / "AreaPanel.tsx").read_text( + encoding="utf-8" + ) + dataset_panel = (ROOT / "frontend" / "src" / "components" / "datasets" / "DatasetPanel.tsx").read_text( + encoding="utf-8" + ) + + assert "compact-form" in project_panel + assert "entity-card-active" in project_panel + assert "AOI manager" in area_panel + assert "compact-form" in area_panel + assert "entity-card-active" in area_panel + assert "dataset-upload-form" in dataset_panel + assert "dataset-card" in dataset_panel + assert "status-badge-ready" in dataset_panel + + +def test_map_and_ai_workspaces_use_task_blocks_not_raw_stacks() -> None: + map_workspace = (ROOT / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text( + encoding="utf-8" + ) + detection_lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text( + encoding="utf-8" + ) + segmentation_lab = ( + ROOT / "frontend" / "src" / "components" / "segmentation" / "SegmentationLab.tsx" + ).read_text(encoding="utf-8") + styles = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") + + assert "map-toolbar" in map_workspace + assert "layer-control-card" in map_workspace + assert "Spatial review" in map_workspace + assert "model-list" in detection_lab + assert "lab-block" in detection_lab + assert "lab-form-grid" in detection_lab + assert "model-list" in segmentation_lab + assert "lab-block" in segmentation_lab + assert ".dataset-upload-form" in styles + assert ".map-toolbar" in styles + assert ".workspace-grid-ai .lab-form-grid" in styles diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index f2aa1729..0bc3aaaa 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -2067,3 +2067,27 @@ Limitations: Next recommended pass: - Run full readiness, rebuild/deploy Tower, then perform a live browser smoke through the new workbench navigation. + +## Sprint 50 Workspace usability polish (2026-06-17) + +Changed: +- Refined the task-based shell workspaces after the first UI refactor. +- Converted Project, AOI and Dataset panels into compact forms and card-based lists for faster scanning. +- Converted the Map workspace controls into a toolbar with dedicated AOI/layer controls and status. +- Converted Detection Lab and Segmentation Lab into model, run, result and QA blocks. +- Added CSS utilities for entity cards, dataset cards, model cards, lab blocks, primary/secondary actions and responsive nested forms. +- Added regression coverage for the polished workspace structure. + +Tested: +- `cd frontend && npm run typecheck` +- `cd frontend && npm run build` +- `cd backend && python -m pytest tests/test_sprint47_workbench_interaction_smoke.py tests/test_sprint49_workbench_shell_refactor.py -q` + +Open: +- Run full release readiness and deploy the polish to Tower. + +Limitations: +- This pass remains frontend-only. It does not add features, change API contracts, alter migrations, fetch live providers or enable new AI models. + +Next recommended pass: +- Deploy to Tower and verify the Data, Map and AI Labs workspaces on `http://192.168.10.150:1202`. diff --git a/frontend/src/components/datasets/DatasetPanel.tsx b/frontend/src/components/datasets/DatasetPanel.tsx index 5b4adc7f..f07beb18 100644 --- a/frontend/src/components/datasets/DatasetPanel.tsx +++ b/frontend/src/components/datasets/DatasetPanel.tsx @@ -61,70 +61,106 @@ export function DatasetPanel({ onLoadDatasetDetails, onRefreshMetadata, }: DatasetPanelProps) { + const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length + return (
-

Datasets

- - - onDatasetFormChange((previous) => ({ ...previous, source: event.target.value }))} - placeholder="user_upload" - /> - - onDatasetFormChange((previous) => ({ ...previous, file: event.target.files?.[0] ?? null }))} - /> - {loadingDatasets ?

Loading datasets...

: null} {datasets.length === 0 ?

No datasets yet

: null} -
    +
      {datasets.map((dataset) => ( -
    • - {dataset.name} -
      type: {dataset.dataset_type}
      -
      status: {dataset.status}
      -
      readiness: {dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'}
      -
      size: {formatBytes(dataset.size_bytes)}
      -
      features: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'n/a'}
      -
      bbox: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)}
      - - +
    • +
      +
      + {dataset.name} +
      + {dataset.dataset_type} + {dataset.dataset_role ?? 'source'} + {dataset.status} +
      +
      + + {dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'} + +
      +
      + size: {formatBytes(dataset.size_bytes)} + features: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'n/a'} + bbox: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)} +
      +
      + + +
    • ))}
    diff --git a/frontend/src/components/detection/DetectionLab.tsx b/frontend/src/components/detection/DetectionLab.tsx index 5f324806..7ecca5df 100644 --- a/frontend/src/components/detection/DetectionLab.tsx +++ b/frontend/src/components/detection/DetectionLab.tsx @@ -86,60 +86,81 @@ export function DetectionLab({ }: DetectionLabProps): JSX.Element { return (
    -

    Detection Lab

    - +
    +
    +

    Object detection

    +

    Detection Lab

    +
    + +
    {loadingDetectionModels ?

    Loading detection models...

    : null} {detectionModelError ?

    {detectionModelError}

    : null} {detectionModels.length === 0 && !loadingDetectionModels ?

    No detection models reported by backend

    : null} -
      +
        {detectionModels.map((model) => ( -
      • +
      • {model.display_name} -
        model: {model.model_id}
        -
        framework: {model.framework}
        -
        task: {model.task_type}
        -
        status: {model.status}
        -
        configured: {model.configured ? 'yes' : 'no'}
        -
        classes: {model.supported_classes.join(', ')}
        -
        limitation: {model.limitation_message}
        + {model.status} +
        + {model.model_id} + {model.framework} + {model.task_type} +
        +

        classes: {model.supported_classes.join(', ')}

        +

        {model.limitation_message}

      • ))}
      -
      - - - onSetConfidenceThreshold(Number(event.target.value))} - /> +
      +

      Run detection

      +
      + + + +
      {selectedDetectionModelId === 'yolo-configured' ? ( - onSetTileManifestPath(event.target.value)} - /> + ) : null} -
      @@ -154,34 +175,47 @@ export function DetectionLab({ {detectionRunResult.error_code ?

      Code: {detectionRunResult.error_code}

      : null}
      ) : null} -
      -

      Detection results

      - - - onSetClassFilter(event.target.value)} - /> - onSetMinConfidenceFilter(Number(event.target.value))} - /> - +
      +
      + + + +
      + {loadingDetectionResults ?

      Loading detection results...

      : null} @@ -209,17 +243,20 @@ export function DetectionLab({ ) : null} -
      +

      Detection QA

      - - {detectionQaError ?

      {detectionQaError}

      : null} diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index d0b0f1ae..2683d1ec 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -44,11 +44,17 @@ export function MapWorkspace({ }: MapWorkspaceProps): JSX.Element { return (
      -

      Map workspace

      -

      {mapLayerLabel}

      -
      +
      +
      +

      Spatial review

      +

      Map workspace

      +
      + {mapFeatureCollection ? `${mapFeatureCount} features` : 'no layer'} +
      + +
      - -
      -

      Feature inspector

      +
      +

      Feature inspector

      + {selectedMapFeature?.geometry?.type ?? 'none'} +
      {selectedMapFeature ? ( <> -

      Geometry: {selectedMapFeature.geometry?.type ?? 'n/a'}

      {JSON.stringify(selectedMapFeature.properties ?? {}, null, 2)}
      ) : ( diff --git a/frontend/src/components/project/AreaPanel.tsx b/frontend/src/components/project/AreaPanel.tsx index 7eea8c17..d0f6b6bf 100644 --- a/frontend/src/components/project/AreaPanel.tsx +++ b/frontend/src/components/project/AreaPanel.tsx @@ -32,38 +32,57 @@ export function AreaPanel({ }: AreaPanelProps): JSX.Element { return (
      -

      Area manager

      -

      {selectedProject ? `Selected project: ${selectedProject.name}` : 'Select a project first'}

      +
      +
      +

      Spatial scope

      +

      AOI manager

      +
      + {areas.length} +
      +

      {selectedProject ? `Project: ${selectedProject.name}` : 'Select a project first'}

      -
      - onUpdateAreaForm({ ...areaForm, name: event.target.value })} - placeholder="AOI name" - /> - onUpdateAreaForm({ ...areaForm, crs: event.target.value })} - placeholder="EPSG:4326" - /> -