From e4b09c4e4545271fd555cb6ebcb929acc4fe95de Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 17 Jun 2026 21:05:15 +0200 Subject: [PATCH] Polish QA and export workspaces --- CHANGELOG.md | 8 + .../test_sprint51_quality_export_polish.py | 38 +++++ docs/CODEX_EXECUTION_LOG.md | 24 +++ docs/TODO.md | 5 +- .../src/components/exports/ExportCenter.tsx | 134 +++++++++++----- .../src/components/exports/ExportPreview.tsx | 10 +- .../quality/QualityResultsPanel.tsx | 89 +++++++++-- frontend/src/styles/app.css | 151 +++++++++++++++++- 8 files changed, 391 insertions(+), 68 deletions(-) create mode 100644 backend/tests/test_sprint51_quality_export_polish.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fccf90f0..7764bd16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -579,3 +579,11 @@ Added: - Added `scripts/verify_demo_export_workflow.sh` to smoke test demo seeding, QA/QC visibility, metadata/report/vector exports, export listing and artifact downloads through the browser-facing URL. - Added backend tests for export persistence, artifact writing, raster rejection, HTML report creation, canonical export envelopes and raw file downloads. - No migrations, new dependencies, live provider fetching, AI inference, LiDAR, Copilot, Training Studio or separate Reports module were introduced. + +## Sprint 51 QA/QC and export workspace polish (2026-06-17) + +- Polished the QA/QC workspace with persisted-check summary tiles, clearer empty state, quality-check cards and metric chips. +- Polished the Exports workspace with artifact action groups, latest-export card, export history cards and preview panel framing. +- Preserved existing API clients, callbacks and export/QA behavior. +- Added regression coverage for the QA/QC and Exports workspace structure. +- No API contracts, migrations, product capabilities, live provider fetching or AI/model dependency changes were introduced. diff --git a/backend/tests/test_sprint51_quality_export_polish.py b/backend/tests/test_sprint51_quality_export_polish.py new file mode 100644 index 00000000..960918a0 --- /dev/null +++ b/backend/tests/test_sprint51_quality_export_polish.py @@ -0,0 +1,38 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_quality_panel_uses_workbench_summary_and_cards() -> None: + quality_panel = ( + ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx" + ).read_text(encoding="utf-8") + + assert 'className="quality-results-panel"' in quality_panel + assert 'className="quality-summary-grid"' in quality_panel + assert 'className="quality-check-list"' in quality_panel + assert 'className="quality-check-card"' in quality_panel + assert "No persisted QA/QC results yet" in quality_panel + assert "check.metrics.map" in quality_panel + assert "fetch(" not in quality_panel + + +def test_export_center_uses_artifact_actions_and_cards() -> None: + export_center = (ROOT / "frontend" / "src" / "components" / "exports" / "ExportCenter.tsx").read_text( + encoding="utf-8" + ) + export_preview = (ROOT / "frontend" / "src" / "components" / "exports" / "ExportPreview.tsx").read_text( + encoding="utf-8" + ) + + assert 'className="export-center"' in export_center + assert 'className="export-action-grid"' in export_center + assert 'className="latest-export-card"' in export_center + assert 'className="export-list"' in export_center + assert 'className="export-card"' in export_center + assert "onExportDataset" in export_center + assert "onExportDetectionRun" in export_center + assert "onExportSegmentationRun" in export_center + assert "download-only HTML artifact" in export_center + assert 'className="export-preview-panel"' in export_preview diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index 0bc3aaaa..7fa0306d 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -2091,3 +2091,27 @@ Limitations: Next recommended pass: - Deploy to Tower and verify the Data, Map and AI Labs workspaces on `http://192.168.10.150:1202`. + +## Sprint 51 QA/QC and export workspace polish (2026-06-17) + +Changed: +- Refined the QA/QC workspace so persisted checks are shown as summary tiles, quality-check cards and metric chips instead of a raw nested list. +- Refined the Exports workspace with grouped export actions, latest-export status, artifact history cards and a framed JSON/GeoJSON preview panel. +- Kept the existing `useQualityWorkflow` and `useExportWorkflow` dataflow intact; no API client calls or backend contracts changed. +- Added regression coverage for the polished QA/QC and Exports workspace structure. + +Tested: +- `cd frontend && npm run typecheck` +- `cd backend && python -m pytest tests/test_sprint27_frontend_workflow_hooks.py tests/test_sprint30_workbench_components.py tests/test_sprint47_workbench_interaction_smoke.py -q` +- `cd backend && python -m pytest tests/test_sprint51_quality_export_polish.py -q` +- `cd frontend && npm run build` +- `bash scripts/run_readiness_check.sh` (`207 passed`) + +Open: +- Commit, push, deploy to Tower and verify the browser-facing workbench. + +Limitations: +- This pass remains UI polish only. It does not add product capabilities, change API contracts, alter migrations, fetch live providers or enable new AI models. + +Next recommended pass: +- Verify QA/QC and Exports on the deployed Tower workbench, then continue with selected-object inspector detail tabs. diff --git a/docs/TODO.md b/docs/TODO.md index bde4e605..7e97d780 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -318,4 +318,7 @@ This file now starts with the current implementation status. Older preparation/b - [x] Replace the one-page workflow panel stack with a task-based workbench shell. - [x] Add persistent project/AOI/dataset/layer context. - [x] Move selected dataset details into a persistent inspector. -- [ ] Deploy the shell refactor to Tower and run live browser smoke on port 1202. +- [x] Deploy the shell refactor to Tower and run live browser smoke on port 1202. +- [x] Polish Data, Map and AI Labs workspaces. +- [x] Polish QA/QC and Exports workspaces. +- [ ] Add selected-object inspector detail tabs for project, AOI, dataset, QA check, export and AI run context. diff --git a/frontend/src/components/exports/ExportCenter.tsx b/frontend/src/components/exports/ExportCenter.tsx index 1ab96aa1..42679dee 100644 --- a/frontend/src/components/exports/ExportCenter.tsx +++ b/frontend/src/components/exports/ExportCenter.tsx @@ -54,57 +54,103 @@ export function ExportCenter({ onDownload, }: ExportCenterProps): JSX.Element { const canExportDataset = Boolean(selectedDataset && isVectorDatasetType(selectedDataset.dataset_type)) + const geojsonExports = exports.filter((item) => item.export_type.includes('geojson')).length + const reportExports = exports.filter((item) => item.export_type === 'project_report_html').length return ( -
-

Export Center

- - - - - - +
+
+
+

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'} +
+
+
+ + + + + + +
{exportError ?

{exportError}

: null} {latestExport ? ( -

- Latest export: {latestExport.export_type} {'->'} {latestExport.path} -

+
+ Latest export + {latestExport.export_type} +

{latestExport.path}

+
) : null} - {exports.length === 0 ?

No exports registered yet.

: 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.map((item) => ( -
    • - {item.export_type} -
      status: {item.status}
      -
      path: {item.storage_path}
      -
      export id: {item.id}
      - {canPreviewJson(item) ? ( - + ) : ( + download-only HTML artifact + )} + - ) : ( -
      Preview: download-only HTML artifact
      - )} - +
    • ))}
    diff --git a/frontend/src/components/exports/ExportPreview.tsx b/frontend/src/components/exports/ExportPreview.tsx index c4941694..a22439dd 100644 --- a/frontend/src/components/exports/ExportPreview.tsx +++ b/frontend/src/components/exports/ExportPreview.tsx @@ -8,8 +8,14 @@ export function ExportPreview({ content }: ExportPreviewProps): JSX.Element | nu } return ( -
    -

    Export Preview

    +
    +
    +
    +

    Export Preview

    +

    JSON and GeoJSON content preview for the selected export artifact.

    +
    + preview +
    {JSON.stringify(content, null, 2)}
    ) diff --git a/frontend/src/components/quality/QualityResultsPanel.tsx b/frontend/src/components/quality/QualityResultsPanel.tsx index 70844f6c..567a6535 100644 --- a/frontend/src/components/quality/QualityResultsPanel.tsx +++ b/frontend/src/components/quality/QualityResultsPanel.tsx @@ -13,27 +13,84 @@ export function QualityResultsPanel({ qualityChecksError, onRefresh, }: QualityResultsPanelProps): JSX.Element { + const completedChecks = qualityChecks.filter((check) => check.status === 'ok' || check.status === 'completed').length + const latestCheck = qualityChecks[0] ?? null + return ( -
    -

    QA/QC Results

    - +
    +
    +
    +

    QA/QC Results

    +

    Persisted quality checks from dataset, detection and segmentation comparisons.

    +
    + {qualityChecks.length} checks +
    +
    +
    + Completed + {completedChecks} +
    +
    + Latest score + {latestCheck?.score ?? 'n/a'} +
    +
    + Latest status + {latestCheck?.status ?? 'waiting'} +
    +
    +
    + +
    {qualityChecksError ?

    {qualityChecksError}

    : null} - {qualityChecks.length === 0 ?

    No persisted QA/QC results yet

    : null} -
      + {qualityChecks.length === 0 ? ( +
      + No persisted QA/QC results yet +

      Run dataset, detection or segmentation QA against a reference layer to populate this workspace.

      +
      + ) : null} +
        {qualityChecks.map((check) => ( -
      • - {check.check_type} -
        status: {check.status}
        -
        score: {check.score ?? 'n/a'}
        -
        candidate: {check.candidate_dataset_id ?? 'n/a'}
        -
        reference: {check.reference_dataset_id}
        -
        quality check: {check.id}
        -
          +
        • +
          +
          + {check.check_type} +
          + candidate: {check.candidate_dataset_id ?? 'n/a'} + reference: {check.reference_dataset_id} +
          +
          + + {check.status} + +
          +
          +
          + Score + {check.score ?? 'n/a'} +
          +
          + Metrics + {check.metrics.length} +
          +
          + Quality check + {check.id} +
          +
          +
            {check.metrics.map((metric) => (
          • - {metric.metric_key}: {metric.metric_value ?? 'n/a'} + {metric.metric_key} + {metric.metric_value ?? 'n/a'}
          • ))}
          diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 19777cb4..a1413f48 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -847,7 +847,10 @@ section th { .entity-list, .dataset-list, -.model-list { +.model-list, +.quality-check-list, +.export-list, +.metric-list { display: grid; gap: 0.7rem; padding: 0; @@ -858,7 +861,11 @@ section th { .dataset-card, .model-card, .lab-block, -.layer-control-card { +.layer-control-card, +.quality-check-card, +.export-card, +.latest-export-card, +.empty-state { border: 1px solid var(--line); border-radius: 8px; background: var(--panel-soft); @@ -898,12 +905,18 @@ button.entity-card { .dataset-card, .model-card, -.lab-block { +.lab-block, +.quality-check-card, +.export-card, +.latest-export-card, +.empty-state { padding: 0.85rem; } .dataset-card-header, -.model-card { +.model-card, +.quality-check-header, +.export-card-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 0.75rem; @@ -937,6 +950,131 @@ button.entity-card { width: auto; } +.quality-summary-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.65rem; + margin: 0.75rem 0; +} + +.quality-summary-grid > div { + border: 1px solid var(--line); + border-radius: 8px; + padding: 0.72rem; + background: #ffffff; +} + +.quality-summary-grid span, +.quality-score-row span, +.latest-export-card span, +.metric-list span { + display: block; + color: var(--muted); + font-size: 0.72rem; + font-weight: 800; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.quality-summary-grid strong { + display: block; + margin-top: 0.28rem; + font-size: 1.02rem; + line-height: 1.2; +} + +.quality-score-row { + display: grid; + grid-template-columns: 6rem 5rem minmax(0, 1fr); + gap: 0.65rem; + margin-top: 0.75rem; +} + +.quality-score-row > div { + min-width: 0; + border: 1px solid var(--line); + border-radius: 8px; + padding: 0.55rem; + background: #ffffff; +} + +.quality-score-row strong { + display: block; + margin-top: 0.22rem; + overflow: hidden; + font-size: 0.9rem; + line-height: 1.25; + text-overflow: ellipsis; + white-space: nowrap; +} + +.metric-list { + grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); + margin-top: 0.75rem; +} + +.metric-list li { + display: grid; + gap: 0.16rem; + margin-top: 0; + border: 1px solid var(--line); + border-radius: 8px; + padding: 0.58rem; + background: #ffffff; +} + +.metric-list strong { + font-size: 0.95rem; +} + +.empty-state { + display: grid; + gap: 0.25rem; + margin-top: 0.75rem; + background: #ffffff; +} + +.empty-state p, +.latest-export-card p, +.export-card p { + margin: 0.25rem 0 0; + color: var(--muted); + font-size: 0.88rem; + line-height: 1.4; +} + +.export-action-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.6rem; + margin: 0.8rem 0; +} + +.export-action-grid button { + min-height: 3.2rem; + text-align: left; +} + +.latest-export-card { + display: grid; + gap: 0.2rem; + margin-top: 0.75rem; + border-color: rgba(15, 118, 110, 0.28); + background: var(--accent-soft); +} + +.export-list { + margin-top: 0.8rem; +} + +.export-card p { + overflow-wrap: anywhere; +} + +.export-preview-panel .job-result { + max-height: 38rem; +} + .map-toolbar { display: grid; grid-template-columns: minmax(12rem, 1fr) minmax(10rem, 0.8fr) minmax(10rem, 0.8fr) minmax(14rem, 1fr); @@ -1045,7 +1183,10 @@ button.entity-card { .dataset-upload-form, .map-toolbar, - .lab-form-grid { + .lab-form-grid, + .quality-summary-grid, + .quality-score-row, + .export-action-grid { grid-template-columns: 1fr; }