Polish map workspace density
This commit is contained in:
@@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
# Changelog
|
# 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 and mobile breakpoint grids 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)
|
## 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.
|
- Added selected-summary regions to Project, AOI and Dataset panels so active context is visible before forms.
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def test_map_workspace_mobile_controls_remain_compact() -> None:
|
||||||
|
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert ".map-workspace-shell .map-context-summary" in css
|
||||||
|
assert ".map-workspace-shell .map-toolbar" in css
|
||||||
|
assert ".map-workspace-shell .layer-provenance-rail" in css
|
||||||
|
assert "grid-template-columns: repeat(auto-fit, minmax(8.25rem, 1fr));" in css
|
||||||
|
assert "grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));" in css
|
||||||
|
assert ".map-control-surface," in css
|
||||||
@@ -3074,3 +3074,36 @@ Limitations:
|
|||||||
|
|
||||||
Next recommended pass:
|
Next recommended pass:
|
||||||
- Continue with Map workspace panel hierarchy and layer-control density after this pass is deployed and visually checked.
|
- 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`
|
||||||
|
- Live browser post-deploy check showed desktop Map workspace was structured and overflow-free, but the mobile breakpoint still stacked controls too tall above the map.
|
||||||
|
- Red step: `python -m pytest backend/tests/test_sprint85_map_workspace_density.py -q` then failed on missing compact mobile Map workspace breakpoint contracts.
|
||||||
|
- `python -m pytest backend/tests/test_sprint85_map_workspace_density.py -q` (`4 passed`)
|
||||||
|
- `cd frontend && npm run typecheck`
|
||||||
|
- `cd frontend && npm run build`
|
||||||
|
- `bash scripts/run_readiness_check.sh` (`289 passed`; frontend typecheck/build passed; Alembic single head `202606120900`)
|
||||||
|
- Tower redeploy pending after the final mobile CSS correction.
|
||||||
|
|
||||||
|
Open:
|
||||||
|
- None known before final 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.
|
||||||
|
|||||||
@@ -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 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 Overview workspace panel hierarchy polish for readiness and recommended-action regions.
|
||||||
- [x] Add Data workspace selected-summary and panel density polish.
|
- [x] Add Data workspace selected-summary and panel density polish.
|
||||||
|
- [x] Add Map workspace panel hierarchy and layer-control density polish.
|
||||||
|
|
||||||
## Sprint 8 status
|
## Sprint 8 status
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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.
|
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.
|
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.
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export function MapWorkspace({
|
|||||||
onSetMapLayerOpacity,
|
onSetMapLayerOpacity,
|
||||||
onSelectMapFeature,
|
onSelectMapFeature,
|
||||||
}: MapWorkspaceProps): JSX.Element {
|
}: MapWorkspaceProps): JSX.Element {
|
||||||
|
const selectedMapArea = areas.find((area) => area.id === selectedMapAreaId)
|
||||||
const featureProperties = selectedMapFeature?.properties ?? null
|
const featureProperties = selectedMapFeature?.properties ?? null
|
||||||
const featureSummaryEntries = featureProperties
|
const featureSummaryEntries = featureProperties
|
||||||
? Object.entries(featureProperties)
|
? Object.entries(featureProperties)
|
||||||
@@ -58,7 +59,7 @@ export function MapWorkspace({
|
|||||||
: []
|
: []
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section data-testid="map-workspace">
|
<section className="map-workspace-shell" data-testid="map-workspace">
|
||||||
<div className="panel-title-row">
|
<div className="panel-title-row">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Spatial review</p>
|
<p className="eyebrow">Spatial review</p>
|
||||||
@@ -67,6 +68,25 @@ export function MapWorkspace({
|
|||||||
<span className="count-pill">{mapFeatureCollection ? `${mapFeatureCount} features` : 'no layer'}</span>
|
<span className="count-pill">{mapFeatureCollection ? `${mapFeatureCount} features` : 'no layer'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="map-context-summary" aria-label="Map layer status">
|
||||||
|
<div>
|
||||||
|
<span>Area of interest</span>
|
||||||
|
<strong>{selectedMapArea?.name ?? 'No area selected'}</strong>
|
||||||
|
<small>{areaFeatureCollection ? `${areaFeatureCount} AOI features loaded` : 'AOI overlay disabled'}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Active layer</span>
|
||||||
|
<strong>{mapLayerLabel}</strong>
|
||||||
|
<small>{mapLayerSourceLabel}</small>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Feature state</span>
|
||||||
|
<strong>{mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No layer rendered'}</strong>
|
||||||
|
<small>{mapLayerProvenance}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="map-control-surface" aria-label="Map workspace controls">
|
||||||
<div className="map-toolbar">
|
<div className="map-toolbar">
|
||||||
<label>
|
<label>
|
||||||
Area
|
Area
|
||||||
@@ -150,6 +170,8 @@ export function MapWorkspace({
|
|||||||
<strong>{mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No active vector or result layer'}</strong>
|
<strong>{mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No active vector or result layer'}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{!mapFeatureCollection ? (
|
{!mapFeatureCollection ? (
|
||||||
<div className="empty-state map-empty-state">
|
<div className="empty-state map-empty-state">
|
||||||
<strong>No active vector or result layer</strong>
|
<strong>No active vector or result layer</strong>
|
||||||
@@ -171,6 +193,8 @@ export function MapWorkspace({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
<div className="map-frame-surface">
|
||||||
<GeoMap
|
<GeoMap
|
||||||
data={mapFeatureCollection}
|
data={mapFeatureCollection}
|
||||||
areaData={areaFeatureCollection}
|
areaData={areaFeatureCollection}
|
||||||
@@ -180,6 +204,9 @@ export function MapWorkspace({
|
|||||||
areaOpacity={areaLayerOpacity}
|
areaOpacity={areaLayerOpacity}
|
||||||
onFeatureSelect={onSelectMapFeature}
|
onFeatureSelect={onSelectMapFeature}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="map-inspection-surface">
|
||||||
<div className="feature-inspector">
|
<div className="feature-inspector">
|
||||||
<div className="panel-title-row">
|
<div className="panel-title-row">
|
||||||
<h3>Feature inspector</h3>
|
<h3>Feature inspector</h3>
|
||||||
@@ -203,6 +230,7 @@ export function MapWorkspace({
|
|||||||
<p className="muted">Click a visible map feature to inspect its properties.</p>
|
<p className="muted">Click a visible map feature to inspect its properties.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-3
@@ -2007,12 +2007,96 @@ button.entity-card {
|
|||||||
overflow-wrap: anywhere;
|
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;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
margin-bottom: 0.85rem;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-control-card {
|
.layer-control-card {
|
||||||
@@ -2049,7 +2133,7 @@ button.entity-card {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 0.85fr minmax(0, 1.5fr) 0.85fr;
|
grid-template-columns: 0.85fr minmax(0, 1.5fr) 0.85fr;
|
||||||
gap: 0.65rem;
|
gap: 0.65rem;
|
||||||
margin-bottom: 0.85rem;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer-provenance-rail > div,
|
.layer-provenance-rail > div,
|
||||||
@@ -2645,6 +2729,27 @@ button.entity-card {
|
|||||||
gap: 0.55rem;
|
gap: 0.55rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-workspace-shell .map-context-summary {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(8.25rem, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-workspace-shell .map-toolbar {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(8.25rem, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-workspace-shell .layer-provenance-rail {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-control-surface,
|
||||||
|
.map-frame-surface,
|
||||||
|
.map-inspection-surface {
|
||||||
|
padding: 0.58rem;
|
||||||
|
}
|
||||||
|
|
||||||
.layer-control-card {
|
.layer-control-card {
|
||||||
padding: 0.52rem 0.58rem;
|
padding: 0.52rem 0.58rem;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user