Polish map workspace density
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-06-20 20:03:52 +02:00
parent 1549fd8d7a
commit c571dfe96c
7 changed files with 347 additions and 110 deletions
+2
View File
@@ -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.
+135 -107
View File
@@ -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 (
<section data-testid="map-workspace">
<section className="map-workspace-shell" data-testid="map-workspace">
<div className="panel-title-row">
<div>
<p className="eyebrow">Spatial review</p>
@@ -67,89 +68,110 @@ export function MapWorkspace({
<span className="count-pill">{mapFeatureCollection ? `${mapFeatureCount} features` : 'no layer'}</span>
</div>
<div className="map-toolbar">
<label>
Area
<select
value={selectedMapAreaId}
onChange={(event) => onSelectMapArea(event.target.value)}
disabled={areas.length === 0}
data-testid="map-area-select"
>
<option value="">No area</option>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
</option>
))}
</select>
</label>
<div className="layer-control-card">
<label className="checkbox-row">
<input
checked={areaLayerVisible}
disabled={!areaFeatureCollection}
type="checkbox"
onChange={(event) => onSetAreaLayerVisible(event.target.checked)}
data-testid="map-area-visible"
/>
AOI
</label>
<input
aria-label="Area opacity"
disabled={!areaFeatureCollection}
max="0.7"
min="0.05"
step="0.05"
type="range"
value={areaLayerOpacity}
onChange={(event) => onSetAreaLayerOpacity(Number(event.target.value))}
data-testid="map-area-opacity"
/>
<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 className="layer-control-card">
<label className="checkbox-row">
<input
checked={mapLayerVisible}
disabled={!mapFeatureCollection}
type="checkbox"
onChange={(event) => onSetMapLayerVisible(event.target.checked)}
data-testid="map-layer-visible"
/>
Active layer
</label>
<input
aria-label="Layer opacity"
disabled={!mapFeatureCollection}
max="1"
min="0.05"
step="0.05"
type="range"
value={mapLayerOpacity}
onChange={(event) => onSetMapLayerOpacity(Number(event.target.value))}
data-testid="map-layer-opacity"
/>
</div>
<div className="map-status">
<div>
<span>Active layer</span>
<strong>{mapLayerLabel}</strong>
<span>{areaFeatureCollection ? `${areaFeatureCount} AOI loaded` : 'No AOI loaded'}</span>
<span>{mapFeatureCollection ? `${mapFeatureCount} features loaded` : 'No vector/result layer loaded'}</span>
<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="layer-provenance-rail" aria-label="Active map layer provenance">
<div>
<span>Layer source</span>
<strong>{mapLayerSourceLabel}</strong>
<div className="map-control-surface" aria-label="Map workspace controls">
<div className="map-toolbar">
<label>
Area
<select
value={selectedMapAreaId}
onChange={(event) => onSelectMapArea(event.target.value)}
disabled={areas.length === 0}
data-testid="map-area-select"
>
<option value="">No area</option>
{areas.map((area) => (
<option key={area.id} value={area.id}>
{area.name}
</option>
))}
</select>
</label>
<div className="layer-control-card">
<label className="checkbox-row">
<input
checked={areaLayerVisible}
disabled={!areaFeatureCollection}
type="checkbox"
onChange={(event) => onSetAreaLayerVisible(event.target.checked)}
data-testid="map-area-visible"
/>
AOI
</label>
<input
aria-label="Area opacity"
disabled={!areaFeatureCollection}
max="0.7"
min="0.05"
step="0.05"
type="range"
value={areaLayerOpacity}
onChange={(event) => onSetAreaLayerOpacity(Number(event.target.value))}
data-testid="map-area-opacity"
/>
</div>
<div className="layer-control-card">
<label className="checkbox-row">
<input
checked={mapLayerVisible}
disabled={!mapFeatureCollection}
type="checkbox"
onChange={(event) => onSetMapLayerVisible(event.target.checked)}
data-testid="map-layer-visible"
/>
Active layer
</label>
<input
aria-label="Layer opacity"
disabled={!mapFeatureCollection}
max="1"
min="0.05"
step="0.05"
type="range"
value={mapLayerOpacity}
onChange={(event) => onSetMapLayerOpacity(Number(event.target.value))}
data-testid="map-layer-opacity"
/>
</div>
<div className="map-status">
<strong>{mapLayerLabel}</strong>
<span>{areaFeatureCollection ? `${areaFeatureCount} AOI loaded` : 'No AOI loaded'}</span>
<span>{mapFeatureCollection ? `${mapFeatureCount} features loaded` : 'No vector/result layer loaded'}</span>
</div>
</div>
<div>
<span>Provenance</span>
<strong>{mapLayerProvenance}</strong>
</div>
<div>
<span>Draw state</span>
<strong>{mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No active vector or result layer'}</strong>
<div className="layer-provenance-rail" aria-label="Active map layer provenance">
<div>
<span>Layer source</span>
<strong>{mapLayerSourceLabel}</strong>
</div>
<div>
<span>Provenance</span>
<strong>{mapLayerProvenance}</strong>
</div>
<div>
<span>Draw state</span>
<strong>{mapFeatureCollection ? `${mapFeatureCount} rendered features` : 'No active vector or result layer'}</strong>
</div>
</div>
</div>
{!mapFeatureCollection ? (
<div className="empty-state map-empty-state">
<strong>No active vector or result layer</strong>
@@ -171,37 +193,43 @@ export function MapWorkspace({
)}
</div>
) : null}
<GeoMap
data={mapFeatureCollection}
areaData={areaFeatureCollection}
visible={mapLayerVisible}
opacity={mapLayerOpacity}
areaVisible={areaLayerVisible}
areaOpacity={areaLayerOpacity}
onFeatureSelect={onSelectMapFeature}
/>
<div className="feature-inspector">
<div className="panel-title-row">
<h3>Feature inspector</h3>
<span className="count-pill">{selectedMapFeature?.geometry?.type ?? 'none'}</span>
<div className="map-frame-surface">
<GeoMap
data={mapFeatureCollection}
areaData={areaFeatureCollection}
visible={mapLayerVisible}
opacity={mapLayerOpacity}
areaVisible={areaLayerVisible}
areaOpacity={areaLayerOpacity}
onFeatureSelect={onSelectMapFeature}
/>
</div>
<div className="map-inspection-surface">
<div className="feature-inspector">
<div className="panel-title-row">
<h3>Feature inspector</h3>
<span className="count-pill">{selectedMapFeature?.geometry?.type ?? 'none'}</span>
</div>
{selectedMapFeature ? (
<>
{featureSummaryEntries.length > 0 ? (
<div className="feature-summary-grid" aria-label="Selected feature summary">
{featureSummaryEntries.map(([key, value]) => (
<div className="feature-property-chip" key={key}>
<span>{key}</span>
<strong>{String(value)}</strong>
</div>
))}
</div>
) : null}
<pre className="job-result">{JSON.stringify(selectedMapFeature.properties ?? {}, null, 2)}</pre>
</>
) : (
<p className="muted">Click a visible map feature to inspect its properties.</p>
)}
</div>
{selectedMapFeature ? (
<>
{featureSummaryEntries.length > 0 ? (
<div className="feature-summary-grid" aria-label="Selected feature summary">
{featureSummaryEntries.map(([key, value]) => (
<div className="feature-property-chip" key={key}>
<span>{key}</span>
<strong>{String(value)}</strong>
</div>
))}
</div>
) : null}
<pre className="job-result">{JSON.stringify(selectedMapFeature.properties ?? {}, null, 2)}</pre>
</>
) : (
<p className="muted">Click a visible map feature to inspect its properties.</p>
)}
</div>
</section>
)
+108 -3
View File
@@ -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,
@@ -2645,6 +2729,27 @@ button.entity-card {
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 {
padding: 0.52rem 0.58rem;
}