diff --git a/CHANGELOG.md b/CHANGELOG.md index c4ea0125..96ae15b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ # Changelog +## Sprint 78 Export preview readability polish (2026-06-20) + +- Added compact preview summary cards for JSON/GeoJSON export payloads. +- Wrapped export preview JSON in a scroll-contained shell with a lightweight toolbar. +- Improved long key/value wrapping for large handoff artifacts while preserving the stored payload. +- Added static regression coverage for export preview readability contracts. +- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed. + ## Sprint 77 Inspector mobile visual polish (2026-06-20) - Added compact inspector action button grids for narrow screens. diff --git a/backend/tests/test_sprint78_export_preview_readability.py b/backend/tests/test_sprint78_export_preview_readability.py new file mode 100644 index 00000000..19d7d9ed --- /dev/null +++ b/backend/tests/test_sprint78_export_preview_readability.py @@ -0,0 +1,33 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def test_export_preview_readability_css_contracts() -> None: + css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8") + + assert ".export-preview-summary" in css + assert ".export-preview-summary-card" in css + assert ".export-preview-json-shell" in css + assert ".export-preview-json-toolbar" in css + assert ".export-preview-json-body" in css + assert ".export-preview-json-body .job-result" in css + assert "max-height: 32rem;" in css + assert "overflow-wrap: anywhere;" in css + assert "white-space: pre-wrap;" in css + + +def test_export_preview_component_exposes_summary_and_scroll_shell() -> None: + export_preview = (ROOT / "frontend" / "src" / "components" / "exports" / "ExportPreview.tsx").read_text( + encoding="utf-8" + ) + + assert "previewStats" in export_preview + assert 'className="export-preview-summary"' in export_preview + assert 'className="export-preview-summary-card"' in export_preview + assert 'className="export-preview-json-shell"' in export_preview + assert 'className="export-preview-json-toolbar"' in export_preview + assert 'className="export-preview-json-body"' in export_preview + assert "Root keys" in export_preview + assert "Preview size" in export_preview diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index d9eb4db5..d8df0238 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -2877,3 +2877,27 @@ Limitations: Next recommended pass: - Continue with report/export preview readability polish, especially large JSON/HTML handoff artifacts. + +## Sprint 78 Export preview readability polish (2026-06-20) + +Changed: +- Added preview summary cards for JSON/GeoJSON export payload root type, root key count and rendered preview size. +- Added a scroll-contained export preview JSON shell and toolbar around the existing stored payload preview. +- Added wrapping for long JSON keys/values inside the preview body. +- Added `backend/tests/test_sprint78_export_preview_readability.py`. +- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`. + +Tested: +- Red step: `python -m pytest backend/tests/test_sprint78_export_preview_readability.py -q` failed on missing preview summary/shell CSS and markup contracts. +- `python -m pytest backend/tests/test_sprint78_export_preview_readability.py backend/tests/test_sprint77_inspector_mobile_polish.py backend/tests/test_sprint76_export_system_mobile_polish.py backend/tests/test_sprint51_quality_export_polish.py backend/tests/test_sprint64_export_handoff_polish.py backend/tests/test_sprint17_export_foundation.py -q` (`20 passed`) +- `cd frontend && npm run typecheck` +- `cd frontend && npm run build` + +Open: +- Run full readiness, redeploy Tower and verify live runtime smoke after deployment. + +Limitations: +- Frontend presentation polish only; no export content, export API, artifact storage, migration, provider fetching or AI/model changes. + +Next recommended pass: +- Continue with accessibility/keyboard focus polish across primary workbench controls. diff --git a/docs/TODO.md b/docs/TODO.md index cfae260c..56b597e1 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -67,6 +67,7 @@ This file now starts with the current implementation status. Older preparation/b - [x] Add AI Labs mobile visual polish for model cards, lab forms and result tables. - [x] Add Export/System mobile visual polish for export actions and provider capability cards. - [x] Add inspector mobile polish for dataset metadata, raster/vector tools and action groups. +- [x] Add export preview readability polish for large JSON/GeoJSON handoff artifacts. ## Sprint 8 status diff --git a/frontend/README.md b/frontend/README.md index 62fda5e9..10970cbf 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -12,6 +12,8 @@ Exports and System provider capabilities now use compact, mobile-safe cards for 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. + ## Scope implemented - API client layer (`src/services/api`) - Project and area list/create flows diff --git a/frontend/src/components/exports/ExportPreview.tsx b/frontend/src/components/exports/ExportPreview.tsx index 91f739fa..9efb0928 100644 --- a/frontend/src/components/exports/ExportPreview.tsx +++ b/frontend/src/components/exports/ExportPreview.tsx @@ -2,7 +2,26 @@ interface ExportPreviewProps { content: Record | null } +function formatBytes(value: number): string { + if (value < 1024) { + return `${value} B` + } + if (value < 1024 * 1024) { + return `${(value / 1024).toFixed(1)} KB` + } + return `${(value / (1024 * 1024)).toFixed(1)} MB` +} + export function ExportPreview({ content }: ExportPreviewProps): JSX.Element { + const previewJson = content ? JSON.stringify(content, null, 2) : '' + const previewStats = content + ? { + rootKeys: Object.keys(content).length, + previewSize: formatBytes(new Blob([previewJson]).size), + rootType: Array.isArray(content) ? 'array' : 'object', + } + : null + return (
@@ -13,7 +32,31 @@ export function ExportPreview({ content }: ExportPreviewProps): JSX.Element { preview
{content ? ( -
{JSON.stringify(content, null, 2)}
+ <> +
+
+ Root type + {previewStats?.rootType} +
+
+ Root keys + {previewStats?.rootKeys} +
+
+ Preview size + {previewStats?.previewSize} +
+
+
+
+ Stored JSON payload + {previewStats?.previewSize} +
+
+
{previewJson}
+
+
+ ) : (
No export preview selected. diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 32fc178b..37450b54 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -1771,6 +1771,74 @@ button.entity-card { max-height: 38rem; } +.export-preview-summary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); + gap: 0.55rem; + margin-top: 0.75rem; +} + +.export-preview-summary-card { + display: grid; + gap: 0.14rem; + min-width: 0; + border: 1px solid var(--line); + border-radius: 8px; + padding: 0.62rem; + background: #ffffff; +} + +.export-preview-summary-card span { + color: var(--muted); + font-size: 0.68rem; + font-weight: 800; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.export-preview-summary-card strong { + overflow-wrap: anywhere; + font-size: 0.92rem; + line-height: 1.2; +} + +.export-preview-json-shell { + min-width: 0; + margin-top: 0.75rem; + border: 1px solid var(--line); + border-radius: 8px; + overflow: hidden; + background: #0f172a; +} + +.export-preview-json-toolbar { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + justify-content: space-between; + border-bottom: 1px solid rgba(226, 232, 240, 0.18); + padding: 0.55rem 0.68rem; + color: #cbd5e1; + font-size: 0.78rem; + font-weight: 800; +} + +.export-preview-json-body { + max-height: 32rem; + overflow: auto; +} + +.export-preview-json-body .job-result { + max-height: none; + margin: 0; + border: 0; + border-radius: 0; + background: #0f172a; + color: #e2e8f0; + white-space: pre-wrap; + overflow-wrap: anywhere; +} + .map-toolbar { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));