From 8b4a8271aca2fbf01062e028ce13d7707d928ae7 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 20 Jun 2026 19:59:01 +0200 Subject: [PATCH] Polish map workspace density --- CHANGELOG.md | 8 + .../test_sprint85_map_workspace_density.py | 48 ++++ docs/CODEX_EXECUTION_LOG.md | 27 ++ docs/TODO.md | 1 + frontend/README.md | 2 + frontend/src/components/map/MapWorkspace.tsx | 242 ++++++++++-------- frontend/src/styles/app.css | 90 ++++++- 7 files changed, 308 insertions(+), 110 deletions(-) create mode 100644 backend/tests/test_sprint85_map_workspace_density.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f649eec..315b5173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ # Changelog +## Sprint 85 Map workspace density polish (2026-06-20) + +- Added a compact Map workspace context summary for selected AOI, active layer and rendered feature state. +- Grouped map controls, provenance, map frame and feature inspector into clearer surfaces without changing MapLibre behavior. +- Tightened map toolbar/provenance spacing so Map workspace scans better on desktop and narrow screens. +- Added static regression coverage for Map workspace hierarchy and density contracts. +- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed. + ## Sprint 84 Data workspace density polish (2026-06-20) - Added selected-summary regions to Project, AOI and Dataset panels so active context is visible before forms. diff --git a/backend/tests/test_sprint85_map_workspace_density.py b/backend/tests/test_sprint85_map_workspace_density.py new file mode 100644 index 00000000..3b0abd4d --- /dev/null +++ b/backend/tests/test_sprint85_map_workspace_density.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_map_workspace_exposes_structured_surfaces() -> None: + map_workspace = (ROOT / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text( + encoding="utf-8" + ) + + assert 'className="map-workspace-shell"' in map_workspace + assert 'className="map-context-summary"' in map_workspace + assert 'aria-label="Map layer status"' in map_workspace + assert 'className="map-control-surface"' in map_workspace + assert 'aria-label="Map workspace controls"' in map_workspace + assert 'className="map-frame-surface"' in map_workspace + assert 'className="map-inspection-surface"' in map_workspace + + +def test_map_workspace_summary_uses_current_layer_and_selection_state() -> None: + map_workspace = (ROOT / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text( + encoding="utf-8" + ) + + assert "const selectedMapArea = areas.find" in map_workspace + assert "selectedMapArea?.name ?? 'No area selected'" in map_workspace + assert "mapLayerLabel" in map_workspace + assert "mapLayerSourceLabel" in map_workspace + assert "mapFeatureCount" in map_workspace + assert "areaFeatureCount" in map_workspace + + +def test_map_workspace_density_css_contracts() -> None: + css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") + + assert ".map-workspace-shell" in css + assert ".map-context-summary" in css + assert ".map-context-summary > div" in css + assert ".map-control-surface" in css + assert ".map-frame-surface" in css + assert ".map-inspection-surface" in css + assert ".map-workspace-shell .map-toolbar" in css + assert "grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));" in css + assert "background: linear-gradient(180deg, #ffffff, #f7fbf8);" in css + assert "border-left: 4px solid var(--accent);" in css diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 28a57623..6fe8a24d 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -3074,3 +3074,30 @@ Limitations: Next recommended pass: - Continue with Map workspace panel hierarchy and layer-control density after this pass is deployed and visually checked. + +## Sprint 85 Map workspace density polish (2026-06-20) + +Changed: +- Added a compact Map workspace context summary for selected AOI, active layer and rendered feature state. +- Wrapped existing layer controls and provenance in a dedicated `map-control-surface`. +- Wrapped the existing MapLibre component in a `map-frame-surface` and the selected-feature inspector in a `map-inspection-surface`. +- Added `backend/tests/test_sprint85_map_workspace_density.py`. +- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`. + +Tested: +- Live browser pre-check against `http://192.168.10.150:1202` showed Map workspace had no console errors or horizontal overflow, but controls, provenance, map frame and inspector still read as a loose vertical stack. +- Red step: `python -m pytest backend/tests/test_sprint85_map_workspace_density.py -q` failed on missing Map workspace surface and context-summary contracts. +- `python -m pytest backend/tests/test_sprint85_map_workspace_density.py -q` (`3 passed`) +- `python -m pytest backend/tests/test_sprint85_map_workspace_density.py backend/tests/test_sprint63_map_overlay_ergonomics.py backend/tests/test_sprint67_map_empty_state_quick_actions.py backend/tests/test_sprint74_data_map_mobile_polish.py backend/tests/test_sprint47_workbench_interaction_smoke.py -q` (`13 passed`) +- `cd frontend && npm run typecheck` +- `cd frontend && npm run build` +- Full readiness and Tower deploy pending in this pass. + +Open: +- None known before full readiness/deploy validation. + +Limitations: +- Frontend Map workspace presentation hierarchy only; no overlay behavior, API contract, persistence, migration, provider fetching or AI/model changes. + +Next recommended pass: +- Continue with QA/QC workspace result hierarchy and filter density after this pass is deployed and visually checked. diff --git a/docs/TODO.md b/docs/TODO.md index 6c4c45cb..2136a41a 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -74,6 +74,7 @@ This file now starts with the current implementation status. Older preparation/b - [x] Add compact shell density polish for topbar context, mobile navigation and workspace skip flow. - [x] Add Overview workspace panel hierarchy polish for readiness and recommended-action regions. - [x] Add Data workspace selected-summary and panel density polish. +- [x] Add Map workspace panel hierarchy and layer-control density polish. ## Sprint 8 status diff --git a/frontend/README.md b/frontend/README.md index bfc8b52e..27df1da5 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -6,6 +6,8 @@ The workbench now uses a task-based shell instead of a single long panel stack. Data and Map workspaces include mobile-density CSS for file inputs, dataset action buttons, map toolbar controls, range sliders and empty-map quick actions so narrow screens avoid accidental viewport overflow. +Map workspace now surfaces the selected AOI, active layer and rendered feature state before controls, then separates layer controls, provenance, the MapLibre frame and the feature inspector into focused surfaces. The underlying overlay, opacity and feature-selection behavior is unchanged. + AI Labs uses the same mobile-density baseline for Detection and Segmentation model cards, run forms, QA/result summaries and scroll-safe result tables. This keeps long model ids, tile paths and artifact paths from widening the workbench on phones. Exports and System provider capabilities now use compact, mobile-safe cards for handoff actions, artifact filters and provider metadata. Long provider limitations, export ids and artifact paths wrap inside their cards instead of widening the workbench. diff --git a/frontend/src/components/map/MapWorkspace.tsx b/frontend/src/components/map/MapWorkspace.tsx index e37bdabe..2fc3fdd2 100644 --- a/frontend/src/components/map/MapWorkspace.tsx +++ b/frontend/src/components/map/MapWorkspace.tsx @@ -50,6 +50,7 @@ export function MapWorkspace({ onSetMapLayerOpacity, onSelectMapFeature, }: MapWorkspaceProps): JSX.Element { + const selectedMapArea = areas.find((area) => area.id === selectedMapAreaId) const featureProperties = selectedMapFeature?.properties ?? null const featureSummaryEntries = featureProperties ? Object.entries(featureProperties) @@ -58,7 +59,7 @@ export function MapWorkspace({ : [] return ( -
+

Spatial review

@@ -67,89 +68,110 @@ export function MapWorkspace({ {mapFeatureCollection ? `${mapFeatureCount} features` : 'no layer'}
-
- -
- - onSetAreaLayerOpacity(Number(event.target.value))} - data-testid="map-area-opacity" - /> +
+
+ Area of interest + {selectedMapArea?.name ?? 'No area selected'} + {areaFeatureCollection ? `${areaFeatureCount} AOI features loaded` : 'AOI overlay disabled'}
-
- - onSetMapLayerOpacity(Number(event.target.value))} - data-testid="map-layer-opacity" - /> -
-
+
+ Active layer {mapLayerLabel} - {areaFeatureCollection ? `${areaFeatureCount} AOI loaded` : 'No AOI loaded'} - {mapFeatureCollection ? `${mapFeatureCount} features loaded` : 'No vector/result layer loaded'} + {mapLayerSourceLabel} +
+
+ Feature state + {mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No layer rendered'} + {mapLayerProvenance}
-
-
- Layer source - {mapLayerSourceLabel} + +
+
+ +
+ + onSetAreaLayerOpacity(Number(event.target.value))} + data-testid="map-area-opacity" + /> +
+
+ + onSetMapLayerOpacity(Number(event.target.value))} + data-testid="map-layer-opacity" + /> +
+
+ {mapLayerLabel} + {areaFeatureCollection ? `${areaFeatureCount} AOI loaded` : 'No AOI loaded'} + {mapFeatureCollection ? `${mapFeatureCount} features loaded` : 'No vector/result layer loaded'} +
-
- Provenance - {mapLayerProvenance} -
-
- Draw state - {mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No active vector or result layer'} +
+
+ Layer source + {mapLayerSourceLabel} +
+
+ Provenance + {mapLayerProvenance} +
+
+ Draw state + {mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No active vector or result layer'} +
+ {!mapFeatureCollection ? (
No active vector or result layer @@ -171,37 +193,43 @@ export function MapWorkspace({ )}
) : null} - -
-
-

Feature inspector

- {selectedMapFeature?.geometry?.type ?? 'none'} + +
+ +
+ +
+
+
+

Feature inspector

+ {selectedMapFeature?.geometry?.type ?? 'none'} +
+ {selectedMapFeature ? ( + <> + {featureSummaryEntries.length > 0 ? ( +
+ {featureSummaryEntries.map(([key, value]) => ( +
+ {key} + {String(value)} +
+ ))} +
+ ) : null} +
{JSON.stringify(selectedMapFeature.properties ?? {}, null, 2)}
+ + ) : ( +

Click a visible map feature to inspect its properties.

+ )}
- {selectedMapFeature ? ( - <> - {featureSummaryEntries.length > 0 ? ( -
- {featureSummaryEntries.map(([key, value]) => ( -
- {key} - {String(value)} -
- ))} -
- ) : null} -
{JSON.stringify(selectedMapFeature.properties ?? {}, null, 2)}
- - ) : ( -

Click a visible map feature to inspect its properties.

- )}
) diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index a8bac4f7..d02e04ac 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -2007,12 +2007,96 @@ button.entity-card { overflow-wrap: anywhere; } -.map-toolbar { +.map-workspace-shell { + display: grid; + gap: 0.75rem; +} + +.map-context-summary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); + gap: 0.6rem; +} + +.map-context-summary > div { + min-width: 0; + border: 1px solid var(--line); + border-left: 4px solid var(--accent); + border-radius: 8px; + padding: 0.68rem 0.78rem; + background: linear-gradient(180deg, #ffffff, #f7fbf8); +} + +.map-context-summary span, +.map-context-summary small { + display: block; +} + +.map-context-summary span { + color: var(--muted); + font-size: 0.68rem; + font-weight: 850; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.map-context-summary strong { + display: block; + margin-top: 0.2rem; + overflow: hidden; + color: var(--ink); + font-size: 0.98rem; + line-height: 1.22; + text-overflow: ellipsis; + white-space: nowrap; +} + +.map-context-summary small { + margin-top: 0.25rem; + overflow: hidden; + color: var(--muted); + font-size: 0.78rem; + line-height: 1.25; + text-overflow: ellipsis; + white-space: nowrap; +} + +.map-control-surface, +.map-frame-surface, +.map-inspection-surface { + min-width: 0; + border: 1px solid var(--line); + border-radius: 8px; + padding: 0.72rem; + background: #ffffff; +} + +.map-control-surface { + display: grid; + gap: 0.68rem; +} + +.map-frame-surface { + padding: 0.52rem; + background: #f7fbf8; +} + +.map-frame-surface .map-container { + margin: 0; +} + +.map-inspection-surface .feature-inspector { + margin-top: 0; + border-top: 0; + padding-top: 0; +} + +.map-workspace-shell .map-toolbar { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: 0.75rem; align-items: end; - margin-bottom: 0.85rem; + margin-bottom: 0; } .layer-control-card { @@ -2049,7 +2133,7 @@ button.entity-card { display: grid; grid-template-columns: 0.85fr minmax(0, 1.5fr) 0.85fr; gap: 0.65rem; - margin-bottom: 0.85rem; + margin-bottom: 0; } .layer-provenance-rail > div,