diff --git a/CHANGELOG.md b/CHANGELOG.md index e100ced9..0c5cf655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ # Changelog +## Sprint 89 Export/System density polish (2026-06-20) + +- Grouped Export Center summary, handoff readiness, artifact actions, state cards and history into focused surfaces. +- Grouped Provider Capabilities into a system shell with registry state cards, capability cards and attribution/license provenance cards. +- Preserved existing export action, filter, preview/download and provider refresh workflows without API or persistence changes. +- Added static regression coverage for Export/System hierarchy and density contracts. +- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed. + ## Sprint 88 AI Labs density polish (2026-06-20) - Grouped Detection Lab and Segmentation Lab model registry, run controls, result loading and QA controls into focused surfaces. diff --git a/backend/tests/test_sprint89_export_system_density.py b/backend/tests/test_sprint89_export_system_density.py new file mode 100644 index 00000000..91a1719b --- /dev/null +++ b/backend/tests/test_sprint89_export_system_density.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_export_center_exposes_final_handoff_surfaces() -> None: + export_center = (ROOT / "frontend" / "src" / "components" / "exports" / "ExportCenter.tsx").read_text( + encoding="utf-8" + ) + + assert 'className="export-center"' in export_center + assert 'className="export-center-shell"' in export_center + assert 'className="export-summary-surface"' in export_center + assert 'aria-label="Export summary"' in export_center + assert 'className="export-handoff-surface"' in export_center + assert 'aria-label="Export handoff readiness"' in export_center + assert 'className="export-actions-surface"' in export_center + assert 'aria-label="Export artifact actions"' in export_center + assert 'className="export-state-stack"' in export_center + assert 'className="export-history-surface"' in export_center + assert 'aria-label="Export history"' in export_center + + +def test_provider_panel_exposes_final_system_surfaces() -> None: + provider_panel = (ROOT / "frontend" / "src" / "components" / "providers" / "ProviderPanel.tsx").read_text( + encoding="utf-8" + ) + + assert 'className="system-provider-panel"' in provider_panel + assert 'className="system-provider-shell"' in provider_panel + assert 'className="system-provider-state-stack"' in provider_panel + assert 'className="system-provider-capability-surface"' in provider_panel + assert 'aria-label="Provider capability registry"' in provider_panel + assert 'className="provider-provenance-grid"' in provider_panel + assert 'className="provider-provenance-card"' in provider_panel + assert 'className="provider-detail-stack"' in provider_panel + + +def test_export_system_preserves_existing_workflow_controls() -> None: + export_center = (ROOT / "frontend" / "src" / "components" / "exports" / "ExportCenter.tsx").read_text( + encoding="utf-8" + ) + provider_panel = (ROOT / "frontend" / "src" / "components" / "providers" / "ProviderPanel.tsx").read_text( + encoding="utf-8" + ) + + assert "onExportDataset" in export_center + assert "onExportDetectionRun" in export_center + assert "onExportSegmentationRun" in export_center + assert "onExportProjectMetadata" in export_center + assert "onExportProjectReport" in export_center + assert "onPreviewContent" in export_center + assert "onDownload" in export_center + assert "exportTypeFilter" in export_center + assert "exportStatusFilter" in export_center + assert "exportSearchQuery" in export_center + assert "system-provider-list" in provider_panel + assert "system-provider-card" in provider_panel + assert "provider-layer-chip" in provider_panel + assert "provider.not_configured_reason" in provider_panel + + +def test_export_system_density_css_contracts() -> None: + css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") + + assert ".export-center-shell" in css + assert ".export-summary-surface" in css + assert ".export-handoff-surface" in css + assert ".export-actions-surface" in css + assert ".export-state-stack" in css + assert ".export-history-surface" in css + assert ".export-center-shell .quality-summary-grid" in css + assert ".system-provider-shell" in css + assert ".system-provider-state-stack" in css + assert ".system-provider-capability-surface" in css + assert ".provider-detail-stack" in css + assert ".provider-provenance-grid" in css + assert ".provider-provenance-card" in css + assert "grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));" in css + assert "background: linear-gradient(180deg, #ffffff, #f7fbf8);" in css diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 944d923b..7b65c151 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -3210,3 +3210,38 @@ Limitations: Next recommended pass: - Continue with export/system final visual consistency or live workflow guidance once this pass is deployed and visually checked. + +## Sprint 89 Export/System density polish (2026-06-20) + +Changed: +- Wrapped Export Center content in `export-center-shell`. +- Grouped export summary, handoff readiness, artifact actions, current state cards and history into focused `export-*` surfaces. +- Wrapped Provider Capabilities content in `system-provider-shell`. +- Replaced loose provider loading/error/empty text with shared `result-state` cards. +- Grouped provider registry content into `system-provider-capability-surface`. +- Split provider limitation/reason text and attribution/license data into `provider-detail-stack` and provenance cards. +- Added shared Export/System density CSS for compact summary, handoff, action, history and provider provenance grids. +- Added `backend/tests/test_sprint89_export_system_density.py`. +- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`. + +Tested: +- Red step: `python -m pytest backend/tests/test_sprint89_export_system_density.py -q` failed on missing Export/System shells, surfaces and CSS contracts. +- `python -m pytest backend/tests/test_sprint89_export_system_density.py backend/tests/test_sprint51_quality_export_polish.py backend/tests/test_sprint64_export_handoff_polish.py backend/tests/test_sprint76_export_system_mobile_polish.py backend/tests/test_sprint78_export_preview_readability.py -q` (`12 passed`) +- `cd frontend && npm run typecheck` +- `cd frontend && npm run build` +- In-app browser fallback note: the Browser click path timed out on the local workspace navigation CDP command, so rendered validation used local Chrome via Playwright. +- Local Chrome UI check against `http://127.0.0.1:5174` passed on desktop and mobile: Export/System shells and surfaces rendered with no horizontal overflow. Local console showed expected Vite proxy 500s because only the frontend server was running. +- `bash scripts/run_readiness_check.sh` (`303 passed`; frontend typecheck/build passed; Alembic head `202606120900`; live smoke syntax passed) +- Tower deploy via `scripts/deploy_tower.ps1` rebuilt and restarted the all-in-one container on `http://192.168.10.150:1202`. +- Tower deploy live migration smoke passed with PostGIS `3.6 USE_GEOS=1 USE_PROJ=1 USE_STATS=1` and required runtime schema objects present. +- Browser runtime verification passed for frontend, proxied API and icon. +- Live Chrome UI check against `http://192.168.10.150:1202` passed on desktop and mobile: Export/System shells and surfaces rendered, four provider cards and eight provider provenance cards were visible, no horizontal overflow and no console warnings/errors. + +Open: +- None known after full readiness, Tower deploy and live browser validation. + +Limitations: +- Frontend Export/System presentation hierarchy only; no export behavior, API contract, persistence, migration, provider fetching or AI/model changes. + +Next recommended pass: +- Continue with end-to-end workflow guidance after this pass is deployed and visually checked. diff --git a/docs/TODO.md b/docs/TODO.md index 08ffe036..be60f671 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -78,6 +78,7 @@ This file now starts with the current implementation status. Older preparation/b - [x] Add QA/QC workspace result hierarchy and filter density polish. - [x] Add Change Detection panel hierarchy and analysis workspace density polish. - [x] Add AI Labs Detection/Segmentation hierarchy and result density polish. +- [x] Add Export/System handoff hierarchy and provider registry density polish. ## Sprint 8 status diff --git a/frontend/README.md b/frontend/README.md index f6ce38d7..8cdc6fab 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -16,6 +16,8 @@ AI Labs uses the same mobile-density baseline for Detection and Segmentation mod 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. +Export Center now separates export summary, handoff readiness, artifact actions, current export states and export history into focused surfaces. Provider Capabilities uses the same system-workspace rhythm with loading/error/empty cards, provider capability cards and dedicated attribution/license provenance cards. + The right-side inspector also uses mobile-safe metadata and tool panels. Dataset filenames, checksums, bounds, job JSON, raster/vector operation inputs and inspector action groups wrap or scroll within the inspector instead of widening the workbench. Export Preview shows a compact JSON summary before the payload and keeps large JSON/GeoJSON handoff artifacts inside a scroll-contained preview shell. HTML report artifacts remain download-only. diff --git a/frontend/src/components/exports/ExportCenter.tsx b/frontend/src/components/exports/ExportCenter.tsx index 7c6b488a..13f79c23 100644 --- a/frontend/src/components/exports/ExportCenter.tsx +++ b/frontend/src/components/exports/ExportCenter.tsx @@ -105,238 +105,265 @@ export function ExportCenter({ return (
-
-
-

Export Center

-

Create handoff artifacts from persisted datasets, AI runs and project metadata.

-
- {exports.length} exports -
-
-
- GeoJSON - {geojsonExports} -
-
- Reports - {reportExports} -
-
- Selected dataset - {selectedDataset?.name ?? 'none'} -
-
-
+
-

Handoff readiness

-

Pick the artifact that matches the current review state before sharing data outside the workbench.

+

Export Center

+

Create handoff artifacts from persisted datasets, AI runs and project metadata.

- {handoffStatus} + {exports.length} exports
-
-
- Vector dataset - {canExportDataset ? selectedDataset?.name : 'none ready'} -
-
- Detection run - {selectedDetectionRunId || 'none'} -
-
- Segmentation run - {selectedSegmentationRunId || 'none'} -
-
- Latest handoff artifact - {latestExport ? formatExportType(latestExport.export_type) : 'none'} -
-
-
-
-
-
- Refresh - Export registry -

Reload the persisted artifact list for the selected project.

- -
-
- Project report - HTML summary -

Generate the human-readable V1 handoff report from persisted project state.

- -
-
- Metadata - Project JSON -

Export project, dataset, QA and artifact metadata for machine-readable review.

- -
-
- Vector dataset - Selected GeoJSON -

Export the active vector/reference dataset when it is selected and ready.

- -
-
- Detection run - Detections GeoJSON -

Export persisted detection geometries for the selected analysis run.

- -
-
- Segmentation run - Segmentations GeoJSON -

Export persisted segmentation polygons for the selected analysis run.

- -
-
-
- {loadingExports ? ( -
- Loading export registry. -

Retrieving persisted artifacts for the selected project.

-
- ) : null} - {exportError ? ( -
- Export action failed. -

{exportError}

-
- ) : null} - {latestExport ? ( -
- Latest export - {formatExportType(latestExport.export_type)} -

{latestExport.path}

-
- ) : null} - {exports.length === 0 ? ( -
- No exports registered yet. -

Create metadata, GeoJSON or HTML report artifacts once the active project has data to hand off.

-
- ) : null} - {exports.length > 0 ? ( -
- - - - -
- ) : null} - {filteredExports.length > 10 ? ( -
- - Showing {visibleExports.length} of {filteredExports.length} matching exports. - {hiddenExportCount > 0 ? ` ${hiddenExportCount} older artifacts hidden.` : ' All matching artifacts shown.'} - - -
- ) : null} - {exports.length > 0 && filteredExports.length === 0 ? ( -
- No exports match the current filters. -

Clear the search, type or status filter to return to the latest artifacts.

-
- ) : null} - + ) : null} + {filteredExports.length > 10 ? ( +
+ + Showing {visibleExports.length} of {filteredExports.length} matching exports. + {hiddenExportCount > 0 ? ` ${hiddenExportCount} older artifacts hidden.` : ' All matching artifacts shown.'} + + +
+ ) : null} + {exports.length > 0 && filteredExports.length === 0 ? ( +
+ No exports match the current filters. +

Clear the search, type or status filter to return to the latest artifacts.

+
+ ) : null} + +
+
) } diff --git a/frontend/src/components/providers/ProviderPanel.tsx b/frontend/src/components/providers/ProviderPanel.tsx index b337dacc..bf684117 100644 --- a/frontend/src/components/providers/ProviderPanel.tsx +++ b/frontend/src/components/providers/ProviderPanel.tsx @@ -15,62 +15,95 @@ export function ProviderPanel({ }: ProviderPanelProps): JSX.Element { return (
-
-
-

Provider Capabilities

-

Review configured local providers and honest not-configured external sources.

+
+
+
+

Provider Capabilities

+

Review configured local providers and honest not-configured external sources.

+
+ +
+ +
+ {loadingCapabilities ? ( +
+ Loading provider capabilities. +

Checking local, fixture and external provider registry states.

+
+ ) : null} + {capabilitiesError ? ( +
+ Provider registry unavailable. +

{capabilitiesError}

+
+ ) : null} + {providers.length === 0 && !loadingCapabilities ? ( +
+ No providers reported by backend. +

Refresh provider capabilities after the backend is reachable.

+
+ ) : null} +
+ +
+
    + {providers.map((provider) => ( +
  • +
    +
    + {provider.display_name} + {provider.provider_name} +
    + {provider.status} +
    +
    +
    + Authority + {provider.authority_level} +
    +
    + Configured + {provider.configured ? 'yes' : 'no'} +
    +
    + Geometry + {provider.supported_geometry_types.join(', ')} +
    +
    + Query modes + {provider.supported_query_modes.join(', ')} +
    +
    +
    + {provider.supported_layers.map((layer) => ( + + {layer} + + ))} +
    +
    +
    limitation: {provider.limitation_message}
    + {!provider.configured && provider.not_configured_reason ? ( +
    reason: {provider.not_configured_reason}
    + ) : null} +
    +
    +
    + Attribution + {provider.attribution} +
    +
    + License + {provider.license_note} +
    +
    +
  • + ))} +
-
- {loadingCapabilities ?

Loading provider capabilities...

: null} - {capabilitiesError ?

{capabilitiesError}

: null} - {providers.length === 0 && !loadingCapabilities ?

No providers reported by backend

: null} -
    - {providers.map((provider) => ( -
  • -
    -
    - {provider.display_name} - {provider.provider_name} -
    - {provider.status} -
    -
    -
    - Authority - {provider.authority_level} -
    -
    - Configured - {provider.configured ? 'yes' : 'no'} -
    -
    - Geometry - {provider.supported_geometry_types.join(', ')} -
    -
    - Query modes - {provider.supported_query_modes.join(', ')} -
    -
    -
    - {provider.supported_layers.map((layer) => ( - - {layer} - - ))} -
    -
    limitation: {provider.limitation_message}
    -
    attribution: {provider.attribution}
    -
    license: {provider.license_note}
    - {!provider.configured && provider.not_configured_reason ? ( -
    reason: {provider.not_configured_reason}
    - ) : null} -
  • - ))} -
) } diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 50a58645..da2828d4 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -1766,6 +1766,60 @@ button.entity-card { margin: 0.8rem 0; } +.export-center-shell, +.system-provider-shell { + display: grid; + gap: 0.85rem; + min-width: 0; +} + +.export-summary-surface, +.export-handoff-surface, +.export-actions-surface, +.export-history-surface, +.system-provider-capability-surface { + display: grid; + gap: 0.68rem; + min-width: 0; + border: 1px solid var(--line); + border-radius: 8px; + padding: 0.78rem; + background: linear-gradient(180deg, #ffffff, #f7fbf8); + box-shadow: 0 1px 0 rgba(19, 32, 24, 0.03); +} + +.export-history-surface, +.system-provider-capability-surface { + background: #ffffff; +} + +.export-center-shell .quality-summary-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); + gap: 0.58rem; + margin: 0; +} + +.export-handoff-surface .handoff-summary-card { + margin-top: 0; + border: 0; + padding: 0; + background: transparent; + box-shadow: none; +} + +.export-actions-surface .export-action-grid { + margin: 0; +} + +.export-state-stack, +.system-provider-state-stack, +.provider-detail-stack { + display: grid; + gap: 0.5rem; + min-width: 0; +} + .export-action-grid button { min-height: 2.8rem; text-align: left; @@ -2045,6 +2099,43 @@ button.entity-card { font-weight: 800; } +.provider-detail-stack { + color: var(--muted); + font-size: 0.84rem; + line-height: 1.36; +} + +.provider-provenance-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); + gap: 0.5rem; +} + +.provider-provenance-card { + min-width: 0; + border: 1px solid rgba(15, 118, 110, 0.16); + border-radius: 7px; + padding: 0.55rem; + background: #ffffff; +} + +.provider-provenance-card span { + display: block; + color: var(--muted); + font-size: 0.68rem; + font-weight: 800; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.provider-provenance-card strong { + display: block; + margin-top: 0.18rem; + overflow-wrap: anywhere; + font-size: 0.88rem; + line-height: 1.25; +} + .export-preview-panel .job-result { max-height: 38rem; }