Polish workbench result states
This commit is contained in:
@@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Sprint 81 Result state consistency polish (2026-06-20)
|
||||||
|
|
||||||
|
- Added shared result-state styling for compact loading, error, empty and ready states.
|
||||||
|
- Applied consistent state blocks to QA/QC results, export history and AI lab model/result panels.
|
||||||
|
- Replaced loose text/error rows in Detection and Segmentation Labs with scan-friendly state cards.
|
||||||
|
- Added static regression coverage for result-state CSS and panel usage contracts.
|
||||||
|
- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed.
|
||||||
|
|
||||||
## Sprint 80 Operation form readability polish (2026-06-20)
|
## Sprint 80 Operation form readability polish (2026-06-20)
|
||||||
|
|
||||||
- Added structured headings, helper text, field wrappers and action rows to dense raster operation controls.
|
- Added structured headings, helper text, field wrappers and action rows to dense raster operation controls.
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_result_state_css_contracts() -> None:
|
||||||
|
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert ".result-state" in css
|
||||||
|
assert ".result-state-error" in css
|
||||||
|
assert ".result-state-empty" in css
|
||||||
|
assert ".result-state-loading" in css
|
||||||
|
assert ".result-state-ready" in css
|
||||||
|
assert ".result-state strong" in css
|
||||||
|
assert ".result-state p" in css
|
||||||
|
assert "overflow-wrap: anywhere;" in css
|
||||||
|
|
||||||
|
|
||||||
|
def test_quality_and_export_panels_use_result_state_blocks() -> None:
|
||||||
|
quality_panel = (ROOT / "frontend" / "src" / "components" / "quality" / "QualityResultsPanel.tsx").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
export_center = (ROOT / "frontend" / "src" / "components" / "exports" / "ExportCenter.tsx").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert 'className="result-state result-state-error"' in quality_panel
|
||||||
|
assert 'className="result-state result-state-empty"' in quality_panel
|
||||||
|
assert 'className="result-state result-state-error"' in export_center
|
||||||
|
assert 'className="result-state result-state-empty"' in export_center
|
||||||
|
assert 'className="result-state result-state-loading"' in export_center
|
||||||
|
|
||||||
|
|
||||||
|
def test_ai_labs_use_result_state_blocks() -> None:
|
||||||
|
detection_lab = (ROOT / "frontend" / "src" / "components" / "detection" / "DetectionLab.tsx").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
segmentation_lab = (
|
||||||
|
ROOT / "frontend" / "src" / "components" / "segmentation" / "SegmentationLab.tsx"
|
||||||
|
).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
for content in (detection_lab, segmentation_lab):
|
||||||
|
assert 'className="result-state result-state-loading"' in content
|
||||||
|
assert 'className="result-state result-state-error"' in content
|
||||||
|
assert 'className="result-state result-state-empty"' in content
|
||||||
|
assert 'className="result-state result-state-ready"' in content
|
||||||
@@ -2956,3 +2956,30 @@ Limitations:
|
|||||||
|
|
||||||
Next recommended pass:
|
Next recommended pass:
|
||||||
- Continue with compact empty/error-state polish across QA, exports and AI lab result panels.
|
- Continue with compact empty/error-state polish across QA, exports and AI lab result panels.
|
||||||
|
|
||||||
|
## Sprint 81 Result state consistency polish (2026-06-20)
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- Added shared `result-state` CSS variants for loading, error, empty and ready states.
|
||||||
|
- Applied result-state blocks to QA/QC error/empty/filter-empty states.
|
||||||
|
- Applied result-state blocks to export loading, error and empty/filter-empty states.
|
||||||
|
- Applied result-state blocks to Detection and Segmentation model loading/errors, empty registries, result counts and QA/run errors.
|
||||||
|
- Added `backend/tests/test_sprint81_result_state_polish.py`.
|
||||||
|
- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
||||||
|
|
||||||
|
Tested:
|
||||||
|
- Red step: `python -m pytest backend/tests/test_sprint81_result_state_polish.py -q` failed on missing result-state CSS and panel markup contracts.
|
||||||
|
- `python -m pytest backend/tests/test_sprint81_result_state_polish.py -q` (`3 passed`)
|
||||||
|
- `python -m pytest backend/tests/test_sprint81_result_state_polish.py backend/tests/test_sprint80_operation_form_readability.py backend/tests/test_sprint75_ai_labs_mobile_polish.py backend/tests/test_sprint76_export_system_mobile_polish.py backend/tests/test_sprint73_quality_result_filtering.py backend/tests/test_sprint70_quality_handoff_polish.py backend/tests/test_sprint8c_detection_visualization_qa.py backend/tests/test_sprint9_segmentation_foundation.py -q` (`34 passed`)
|
||||||
|
- `cd frontend && npm run typecheck`
|
||||||
|
- `cd frontend && npm run build`
|
||||||
|
- `bash scripts/run_readiness_check.sh` (`276 passed`; frontend typecheck/build passed; Alembic single head `202606120900`)
|
||||||
|
|
||||||
|
Open:
|
||||||
|
- Run full readiness, redeploy Tower and verify live runtime smoke after deployment.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- Frontend presentation/state polish only; no workflow behavior, API contract, persistence, migration, provider fetching or AI/model changes.
|
||||||
|
|
||||||
|
Next recommended pass:
|
||||||
|
- Continue with visual density review for topbar/sidebar/responsive shell after another live browser pass.
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [x] Add export preview readability polish for large JSON/GeoJSON handoff artifacts.
|
- [x] Add export preview readability polish for large JSON/GeoJSON handoff artifacts.
|
||||||
- [x] Add accessibility focus polish for primary workbench keyboard navigation.
|
- [x] Add accessibility focus polish for primary workbench keyboard navigation.
|
||||||
- [x] Add raster/vector operation form readability polish for dense tool panels.
|
- [x] Add raster/vector operation form readability polish for dense tool panels.
|
||||||
|
- [x] Add compact loading/error/empty/result state polish across QA, exports and AI labs.
|
||||||
|
|
||||||
## Sprint 8 status
|
## Sprint 8 status
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ Primary workbench navigation, overview shortcuts, inspector tabs and dataset act
|
|||||||
|
|
||||||
Raster and vector operation panels use structured group headings, compact helper text, field grids, action rows and inline error blocks so dense GIS controls remain scannable in the inspector.
|
Raster and vector operation panels use structured group headings, compact helper text, field grids, action rows and inline error blocks so dense GIS controls remain scannable in the inspector.
|
||||||
|
|
||||||
|
QA/QC, exports and AI lab result panels use shared loading, error, empty and ready state cards. This keeps model registry failures, empty histories and result counts visually consistent across the workbench.
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -95,9 +95,24 @@ export function DetectionLab({
|
|||||||
Refresh models
|
Refresh models
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{loadingDetectionModels ? <p>Loading detection models...</p> : null}
|
{loadingDetectionModels ? (
|
||||||
{detectionModelError ? <p className="error">{detectionModelError}</p> : null}
|
<div className="result-state result-state-loading">
|
||||||
{detectionModels.length === 0 && !loadingDetectionModels ? <p>No detection models reported by backend</p> : null}
|
<strong>Loading detection models.</strong>
|
||||||
|
<p>Checking backend model registry availability.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{detectionModelError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>Detection model registry unavailable.</strong>
|
||||||
|
<p>{detectionModelError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{detectionModels.length === 0 && !loadingDetectionModels ? (
|
||||||
|
<div className="result-state result-state-empty">
|
||||||
|
<strong>No detection models reported by backend.</strong>
|
||||||
|
<p>Refresh models after the backend is reachable.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<ul className="model-list">
|
<ul className="model-list">
|
||||||
{detectionModels.map((model) => (
|
{detectionModels.map((model) => (
|
||||||
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
|
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
|
||||||
@@ -164,7 +179,12 @@ export function DetectionLab({
|
|||||||
Run detection
|
Run detection
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{detectionRunError ? <p className="error">{detectionRunError}</p> : null}
|
{detectionRunError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>Detection run failed.</strong>
|
||||||
|
<p>{detectionRunError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{detectionRunResult ? (
|
{detectionRunResult ? (
|
||||||
<div className="result-summary-card">
|
<div className="result-summary-card">
|
||||||
<p>Status: {detectionRunResult.status}</p>
|
<p>Status: {detectionRunResult.status}</p>
|
||||||
@@ -218,8 +238,16 @@ export function DetectionLab({
|
|||||||
<button className="primary-action" type="button" onClick={onLoadResults} disabled={!selectedDetectionRunId || loadingDetectionResults}>
|
<button className="primary-action" type="button" onClick={onLoadResults} disabled={!selectedDetectionRunId || loadingDetectionResults}>
|
||||||
Load detections
|
Load detections
|
||||||
</button>
|
</button>
|
||||||
{loadingDetectionResults ? <p>Loading detection results...</p> : null}
|
{loadingDetectionResults ? (
|
||||||
<p>Detections loaded: {detectionItems.length}</p>
|
<div className="result-state result-state-loading">
|
||||||
|
<strong>Loading detection results.</strong>
|
||||||
|
<p>Retrieving persisted detections for the selected run.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="result-state result-state-ready">
|
||||||
|
<strong>Detections loaded: {detectionItems.length}</strong>
|
||||||
|
<p>{selectedDetectionRunId ? 'Loaded from persisted detection records.' : 'Select a detection run before loading results.'}</p>
|
||||||
|
</div>
|
||||||
{detectionItems.length > 0 ? (
|
{detectionItems.length > 0 ? (
|
||||||
<div className="table-scroll">
|
<div className="table-scroll">
|
||||||
<table>
|
<table>
|
||||||
@@ -261,7 +289,12 @@ export function DetectionLab({
|
|||||||
<button className="primary-action" type="button" onClick={onRunQa} disabled={runningDetectionQa || !selectedDetectionRunId || !detectionReferenceDatasetId}>
|
<button className="primary-action" type="button" onClick={onRunQa} disabled={runningDetectionQa || !selectedDetectionRunId || !detectionReferenceDatasetId}>
|
||||||
Compare detections to reference
|
Compare detections to reference
|
||||||
</button>
|
</button>
|
||||||
{detectionQaError ? <p className="error">{detectionQaError}</p> : null}
|
{detectionQaError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>Detection QA failed.</strong>
|
||||||
|
<p>{detectionQaError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{detectionQaResult ? (
|
{detectionQaResult ? (
|
||||||
<div className="result-summary-card">
|
<div className="result-summary-card">
|
||||||
<p>Status: {detectionQaResult.status}</p>
|
<p>Status: {detectionQaResult.status}</p>
|
||||||
|
|||||||
@@ -217,7 +217,18 @@ export function ExportCenter({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{exportError ? <p className="error">{exportError}</p> : null}
|
{loadingExports ? (
|
||||||
|
<div className="result-state result-state-loading">
|
||||||
|
<strong>Loading export registry.</strong>
|
||||||
|
<p>Retrieving persisted artifacts for the selected project.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{exportError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>Export action failed.</strong>
|
||||||
|
<p>{exportError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{latestExport ? (
|
{latestExport ? (
|
||||||
<div className="latest-export-card">
|
<div className="latest-export-card">
|
||||||
<span>Latest export</span>
|
<span>Latest export</span>
|
||||||
@@ -226,7 +237,7 @@ export function ExportCenter({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{exports.length === 0 ? (
|
{exports.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="result-state result-state-empty">
|
||||||
<strong>No exports registered yet.</strong>
|
<strong>No exports registered yet.</strong>
|
||||||
<p>Create metadata, GeoJSON or HTML report artifacts once the active project has data to hand off.</p>
|
<p>Create metadata, GeoJSON or HTML report artifacts once the active project has data to hand off.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -291,7 +302,7 @@ export function ExportCenter({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{exports.length > 0 && filteredExports.length === 0 ? (
|
{exports.length > 0 && filteredExports.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="result-state result-state-empty">
|
||||||
<strong>No exports match the current filters.</strong>
|
<strong>No exports match the current filters.</strong>
|
||||||
<p>Clear the search, type or status filter to return to the latest artifacts.</p>
|
<p>Clear the search, type or status filter to return to the latest artifacts.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -157,9 +157,14 @@ export function QualityResultsPanel({
|
|||||||
Refresh QA/QC results
|
Refresh QA/QC results
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{qualityChecksError ? <p className="error">{qualityChecksError}</p> : null}
|
{qualityChecksError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>QA/QC results could not be loaded.</strong>
|
||||||
|
<p>{qualityChecksError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{qualityChecks.length === 0 ? (
|
{qualityChecks.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="result-state result-state-empty">
|
||||||
<strong>No persisted QA/QC results yet</strong>
|
<strong>No persisted QA/QC results yet</strong>
|
||||||
<p>Run dataset, detection or segmentation QA against a reference layer to populate this workspace.</p>
|
<p>Run dataset, detection or segmentation QA against a reference layer to populate this workspace.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -224,7 +229,7 @@ export function QualityResultsPanel({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{qualityChecks.length > 0 && filteredQualityChecks.length === 0 ? (
|
{qualityChecks.length > 0 && filteredQualityChecks.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="result-state result-state-empty">
|
||||||
<strong>No QA/QC results match the current filters.</strong>
|
<strong>No QA/QC results match the current filters.</strong>
|
||||||
<p>Clear the search, status or check type filter to return to the latest quality results.</p>
|
<p>Clear the search, status or check type filter to return to the latest quality results.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -95,9 +95,24 @@ export function SegmentationLab({
|
|||||||
Refresh models
|
Refresh models
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{loadingSegmentationModels ? <p>Loading segmentation models...</p> : null}
|
{loadingSegmentationModels ? (
|
||||||
{segmentationModelError ? <p className="error">{segmentationModelError}</p> : null}
|
<div className="result-state result-state-loading">
|
||||||
{segmentationModels.length === 0 && !loadingSegmentationModels ? <p>No segmentation models reported by backend</p> : null}
|
<strong>Loading segmentation models.</strong>
|
||||||
|
<p>Checking backend model registry availability.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{segmentationModelError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>Segmentation model registry unavailable.</strong>
|
||||||
|
<p>{segmentationModelError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{segmentationModels.length === 0 && !loadingSegmentationModels ? (
|
||||||
|
<div className="result-state result-state-empty">
|
||||||
|
<strong>No segmentation models reported by backend.</strong>
|
||||||
|
<p>Refresh models after the backend is reachable.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<ul className="model-list">
|
<ul className="model-list">
|
||||||
{segmentationModels.map((model) => (
|
{segmentationModels.map((model) => (
|
||||||
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
|
<li className={model.configured ? 'model-card model-card-ready' : 'model-card'} key={model.model_id}>
|
||||||
@@ -159,9 +174,17 @@ export function SegmentationLab({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{!selectedSegmentationModelConfigured ? (
|
{!selectedSegmentationModelConfigured ? (
|
||||||
<p>{selectedSegmentationModelLimitation ?? 'Select a configured segmentation model'}</p>
|
<div className="result-state result-state-empty">
|
||||||
|
<strong>Segmentation model is not ready.</strong>
|
||||||
|
<p>{selectedSegmentationModelLimitation ?? 'Select a configured segmentation model'}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{segmentationRunError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>Segmentation run failed.</strong>
|
||||||
|
<p>{segmentationRunError}</p>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{segmentationRunError ? <p className="error">{segmentationRunError}</p> : null}
|
|
||||||
{segmentationRunResult ? (
|
{segmentationRunResult ? (
|
||||||
<div className="result-summary-card">
|
<div className="result-summary-card">
|
||||||
<p>Status: {segmentationRunResult.status}</p>
|
<p>Status: {segmentationRunResult.status}</p>
|
||||||
@@ -215,8 +238,16 @@ export function SegmentationLab({
|
|||||||
<button className="primary-action" type="button" onClick={onLoadResults} disabled={!selectedSegmentationRunId || loadingSegmentationResults}>
|
<button className="primary-action" type="button" onClick={onLoadResults} disabled={!selectedSegmentationRunId || loadingSegmentationResults}>
|
||||||
Load segmentations
|
Load segmentations
|
||||||
</button>
|
</button>
|
||||||
{loadingSegmentationResults ? <p>Loading segmentation results...</p> : null}
|
{loadingSegmentationResults ? (
|
||||||
<p>Segmentations loaded: {segmentationItems.length}</p>
|
<div className="result-state result-state-loading">
|
||||||
|
<strong>Loading segmentation results.</strong>
|
||||||
|
<p>Retrieving persisted segmentation polygons for the selected run.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="result-state result-state-ready">
|
||||||
|
<strong>Segmentations loaded: {segmentationItems.length}</strong>
|
||||||
|
<p>{selectedSegmentationRunId ? 'Loaded from persisted segmentation records.' : 'Select a segmentation run before loading results.'}</p>
|
||||||
|
</div>
|
||||||
{segmentationItems.length > 0 ? (
|
{segmentationItems.length > 0 ? (
|
||||||
<div className="table-scroll">
|
<div className="table-scroll">
|
||||||
<table>
|
<table>
|
||||||
@@ -262,7 +293,12 @@ export function SegmentationLab({
|
|||||||
<button className="primary-action" type="button" onClick={onRunQa} disabled={runningSegmentationQa || !selectedSegmentationRunId || !segmentationReferenceDatasetId}>
|
<button className="primary-action" type="button" onClick={onRunQa} disabled={runningSegmentationQa || !selectedSegmentationRunId || !segmentationReferenceDatasetId}>
|
||||||
Compare segmentations to reference
|
Compare segmentations to reference
|
||||||
</button>
|
</button>
|
||||||
{segmentationQaError ? <p className="error">{segmentationQaError}</p> : null}
|
{segmentationQaError ? (
|
||||||
|
<div className="result-state result-state-error">
|
||||||
|
<strong>Segmentation QA failed.</strong>
|
||||||
|
<p>{segmentationQaError}</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
{segmentationQaResult ? (
|
{segmentationQaResult ? (
|
||||||
<div className="result-summary-card">
|
<div className="result-summary-card">
|
||||||
<p>Status: {segmentationQaResult.status}</p>
|
<p>Status: {segmentationQaResult.status}</p>
|
||||||
|
|||||||
@@ -1070,7 +1070,8 @@ section th {
|
|||||||
.quality-check-card,
|
.quality-check-card,
|
||||||
.export-card,
|
.export-card,
|
||||||
.latest-export-card,
|
.latest-export-card,
|
||||||
.empty-state {
|
.empty-state,
|
||||||
|
.result-state {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--panel-soft);
|
background: var(--panel-soft);
|
||||||
@@ -1114,11 +1115,56 @@ button.entity-card {
|
|||||||
.quality-check-card,
|
.quality-check-card,
|
||||||
.export-card,
|
.export-card,
|
||||||
.latest-export-card,
|
.latest-export-card,
|
||||||
.empty-state {
|
.empty-state,
|
||||||
|
.result-state {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0.85rem;
|
padding: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.result-state {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.18rem;
|
||||||
|
background: #ffffff;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-state strong {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-state p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
line-height: 1.38;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-state-error {
|
||||||
|
border-color: rgba(153, 27, 27, 0.25);
|
||||||
|
background: rgba(153, 27, 27, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-state-error strong,
|
||||||
|
.result-state-error p {
|
||||||
|
color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-state-empty {
|
||||||
|
border-style: dashed;
|
||||||
|
background: var(--panel-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-state-loading {
|
||||||
|
border-color: rgba(180, 83, 9, 0.24);
|
||||||
|
background: rgba(180, 83, 9, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-state-ready {
|
||||||
|
border-color: rgba(15, 118, 110, 0.28);
|
||||||
|
background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
.dataset-card,
|
.dataset-card,
|
||||||
.model-card,
|
.model-card,
|
||||||
.quality-check-card,
|
.quality-check-card,
|
||||||
|
|||||||
Reference in New Issue
Block a user