Polish export preview readability
This commit is contained in:
@@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
# Changelog
|
# 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)
|
## Sprint 77 Inspector mobile visual polish (2026-06-20)
|
||||||
|
|
||||||
- Added compact inspector action button grids for narrow screens.
|
- Added compact inspector action button grids for narrow screens.
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -2877,3 +2877,27 @@ Limitations:
|
|||||||
|
|
||||||
Next recommended pass:
|
Next recommended pass:
|
||||||
- Continue with report/export preview readability polish, especially large JSON/HTML handoff artifacts.
|
- 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.
|
||||||
|
|||||||
@@ -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 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 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 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
|
## Sprint 8 status
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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
|
## Scope implemented
|
||||||
- API client layer (`src/services/api`)
|
- API client layer (`src/services/api`)
|
||||||
- Project and area list/create flows
|
- Project and area list/create flows
|
||||||
|
|||||||
@@ -2,7 +2,26 @@ interface ExportPreviewProps {
|
|||||||
content: Record<string, unknown> | null
|
content: Record<string, unknown> | 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 {
|
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 (
|
return (
|
||||||
<section className="export-preview-panel">
|
<section className="export-preview-panel">
|
||||||
<div className="panel-title-row">
|
<div className="panel-title-row">
|
||||||
@@ -13,7 +32,31 @@ export function ExportPreview({ content }: ExportPreviewProps): JSX.Element {
|
|||||||
<span className="count-pill">preview</span>
|
<span className="count-pill">preview</span>
|
||||||
</div>
|
</div>
|
||||||
{content ? (
|
{content ? (
|
||||||
<pre className="job-result">{JSON.stringify(content, null, 2)}</pre>
|
<>
|
||||||
|
<div className="export-preview-summary" aria-label="Export preview summary">
|
||||||
|
<div className="export-preview-summary-card">
|
||||||
|
<span>Root type</span>
|
||||||
|
<strong>{previewStats?.rootType}</strong>
|
||||||
|
</div>
|
||||||
|
<div className="export-preview-summary-card">
|
||||||
|
<span>Root keys</span>
|
||||||
|
<strong>{previewStats?.rootKeys}</strong>
|
||||||
|
</div>
|
||||||
|
<div className="export-preview-summary-card">
|
||||||
|
<span>Preview size</span>
|
||||||
|
<strong>{previewStats?.previewSize}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="export-preview-json-shell">
|
||||||
|
<div className="export-preview-json-toolbar">
|
||||||
|
<span>Stored JSON payload</span>
|
||||||
|
<span>{previewStats?.previewSize}</span>
|
||||||
|
</div>
|
||||||
|
<div className="export-preview-json-body">
|
||||||
|
<pre className="job-result">{previewJson}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
<strong>No export preview selected.</strong>
|
<strong>No export preview selected.</strong>
|
||||||
|
|||||||
@@ -1771,6 +1771,74 @@ button.entity-card {
|
|||||||
max-height: 38rem;
|
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 {
|
.map-toolbar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
|
||||||
|
|||||||
Reference in New Issue
Block a user