Polish workbench visual shell
This commit is contained in:
@@ -7,6 +7,16 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Sprint 62 Workbench visual polish (2026-06-18)
|
||||||
|
|
||||||
|
- Added a compact workspace command bar for fast switching between the primary workbench surfaces.
|
||||||
|
- Polished the shell visual system with raised/sunken surfaces, softer shadows, tighter topbar spacing and more consistent panel styling.
|
||||||
|
- Replaced raw empty-state text in project/dataset panels with structured empty-state blocks.
|
||||||
|
- Improved Detection Lab and Segmentation Lab result summaries and wrapped long result tables in scroll-safe containers.
|
||||||
|
- Improved mobile workbench navigation by using horizontal rails for the primary nav and command chips, reducing vertical crowding without adding new behavior.
|
||||||
|
- Added static regression coverage for the visual polish contracts.
|
||||||
|
- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed.
|
||||||
|
|
||||||
## Sprint 61 Golden QA scenario expansion (2026-06-18)
|
## Sprint 61 Golden QA scenario expansion (2026-06-18)
|
||||||
|
|
||||||
- Expanded the deterministic QA/QC golden benchmark from one building scenario to four local fixture scenarios: partial match, perfect match, no-overlap and MultiPolygon match.
|
- Expanded the deterministic QA/QC golden benchmark from one building scenario to four local fixture scenarios: partial match, perfect match, no-overlap and MultiPolygon match.
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_frontend_shell_has_visual_polish_contracts() -> None:
|
||||||
|
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||||
|
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "workspace-command-bar" in app
|
||||||
|
assert "workspace-nav-cluster" in app
|
||||||
|
assert "--surface-raised" in css
|
||||||
|
assert ".workspace-command-bar" in css
|
||||||
|
assert ".workspace-panel" in css
|
||||||
|
assert ".data-density-grid" in css
|
||||||
|
assert ".result-summary-card" in css
|
||||||
|
|
||||||
|
|
||||||
|
def test_primary_panels_use_empty_state_components() -> None:
|
||||||
|
project_panel = (ROOT / "frontend" / "src" / "components" / "project" / "ProjectPanel.tsx").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
dataset_panel = (ROOT / "frontend" / "src" / "components" / "datasets" / "DatasetPanel.tsx").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
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")
|
||||||
|
|
||||||
|
assert "empty-state" in project_panel
|
||||||
|
assert "empty-state" in dataset_panel
|
||||||
|
assert "result-summary-card" in detection_lab
|
||||||
|
assert "result-summary-card" in segmentation_lab
|
||||||
|
assert "table-scroll" in detection_lab
|
||||||
|
assert "table-scroll" in segmentation_lab
|
||||||
@@ -2407,3 +2407,30 @@ Limitations:
|
|||||||
|
|
||||||
Next recommended pass:
|
Next recommended pass:
|
||||||
- Continue with frontend visual polish backlog or add a live golden benchmark smoke only if a running PostGIS environment needs that extra release signal.
|
- Continue with frontend visual polish backlog or add a live golden benchmark smoke only if a running PostGIS environment needs that extra release signal.
|
||||||
|
|
||||||
|
## Sprint 62 Workbench visual polish (2026-06-18)
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- Added a compact workspace command bar under the active workspace heading for quick movement between the main workbench surfaces.
|
||||||
|
- Polished the shared frontend visual system with raised/sunken surface tokens, softer shadows, tighter topbar spacing and consistent workspace panel styling.
|
||||||
|
- Replaced raw project/dataset empty-state text with structured empty-state blocks.
|
||||||
|
- Wrapped Detection Lab and Segmentation Lab result summaries in scan-friendly result cards and long result tables in scroll-safe containers.
|
||||||
|
- Improved mobile navigation density by making the primary nav and command chips horizontal rails on narrow screens.
|
||||||
|
- Added `backend/tests/test_sprint62_frontend_visual_polish.py` to guard the visual polish contracts.
|
||||||
|
- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
||||||
|
|
||||||
|
Tested:
|
||||||
|
- Red step: `cd backend && python -m pytest tests/test_sprint62_frontend_visual_polish.py -q` failed on missing command bar, panel polish and empty/result wrappers.
|
||||||
|
- `cd backend && python -m pytest tests/test_sprint62_frontend_visual_polish.py tests/test_sprint49_workbench_shell_refactor.py tests/test_sprint50_workspace_usability_polish.py -q` (`6 passed`)
|
||||||
|
- `cd frontend && npm run typecheck`
|
||||||
|
- `cd frontend && npm run build`
|
||||||
|
- Local Chrome/Playwright visual check against `http://127.0.0.1:5175` with live API proxy: desktop and mobile screenshots captured, no console warnings/errors, no horizontal page overflow.
|
||||||
|
|
||||||
|
Open:
|
||||||
|
- Run full readiness and deploy Tower after this visual polish pass.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- This is a frontend usability polish pass only. It does not add new API behavior, data processing, provider fetching or AI model functionality.
|
||||||
|
|
||||||
|
Next recommended pass:
|
||||||
|
- Continue with map/result overlay ergonomics, especially making selected dataset/result provenance easier to see beside the map.
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [x] Optional final bootstrap-effect extraction.
|
- [x] Optional final bootstrap-effect extraction.
|
||||||
- [x] Decide next V1 stabilization focus: golden dataset expansion or frontend visual polish backlog.
|
- [x] Decide next V1 stabilization focus: golden dataset expansion or frontend visual polish backlog.
|
||||||
- [x] Expand golden QA/QC benchmark coverage across partial, perfect, no-overlap and MultiPolygon scenarios.
|
- [x] Expand golden QA/QC benchmark coverage across partial, perfect, no-overlap and MultiPolygon scenarios.
|
||||||
|
- [x] Improve workbench shell visual polish, mobile navigation density and AI result readability.
|
||||||
|
|
||||||
## Sprint 8 status
|
## Sprint 8 status
|
||||||
|
|
||||||
@@ -332,3 +333,4 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [x] Add browser screenshot artifact automation for visual regression handoff.
|
- [x] Add browser screenshot artifact automation for visual regression handoff.
|
||||||
- [x] Add backend error-envelope audit for expected user-error paths.
|
- [x] Add backend error-envelope audit for expected user-error paths.
|
||||||
- [x] Expand golden datasets beyond the original single building QA fixture pair.
|
- [x] Expand golden datasets beyond the original single building QA fixture pair.
|
||||||
|
- [x] Add workbench visual polish pass for command bar, panel surfaces, empty states and mobile nav density.
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ The workbench now uses a task-based shell instead of a single long panel stack.
|
|||||||
- Area fallback selection now lives with the owning workflow hooks: clip-area selection in `useDatasetWorkflow.ts` and map-area selection in `useMapWorkspaceState.ts`.
|
- Area fallback selection now lives with the owning workflow hooks: clip-area selection in `useDatasetWorkflow.ts` and map-area selection in `useMapWorkspaceState.ts`.
|
||||||
- Offline demo workflow orchestration lives in `src/hooks/useDemoWorkflow.ts`, because it coordinates project, dataset, map, QA/QC, detection, segmentation and export state after the backend fixture seed.
|
- Offline demo workflow orchestration lives in `src/hooks/useDemoWorkflow.ts`, because it coordinates project, dataset, map, QA/QC, detection, segmentation and export state after the backend fixture seed.
|
||||||
- Workbench bootstrap and reload effects live in `src/hooks/useWorkbenchBootstrap.ts`, keeping `App.tsx` focused on composing hooks into panels.
|
- Workbench bootstrap and reload effects live in `src/hooks/useWorkbenchBootstrap.ts`, keeping `App.tsx` focused on composing hooks into panels.
|
||||||
|
- The workbench shell includes a compact command bar, consistent raised/sunken surfaces, structured empty states and scroll-safe AI result tables to keep the V1 workflow usable across desktop and mobile widths.
|
||||||
|
|
||||||
## Workbench shell refactor
|
## Workbench shell refactor
|
||||||
|
|
||||||
@@ -230,6 +231,7 @@ The workbench now uses a task-based shell instead of a single long panel stack.
|
|||||||
- `workbench-inspector`: persistent selected dataset details and raster/vector operation controls.
|
- `workbench-inspector`: persistent selected dataset details and raster/vector operation controls.
|
||||||
- Existing API calls, hooks, MapLibre rendering and QA/AI/export flows are unchanged.
|
- Existing API calls, hooks, MapLibre rendering and QA/AI/export flows are unchanged.
|
||||||
- Stable navigation test anchors use `data-testid="workspace-nav-{workspace}"`.
|
- Stable navigation test anchors use `data-testid="workspace-nav-{workspace}"`.
|
||||||
|
- Mobile workbench navigation uses horizontal rails for the primary nav and command bar, avoiding a tall menu stack before the active workspace content.
|
||||||
|
|
||||||
## Raster dependency visibility
|
## Raster dependency visibility
|
||||||
|
|
||||||
|
|||||||
@@ -459,6 +459,22 @@ function App(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
<p>{activeWorkspaceItem.description}</p>
|
<p>{activeWorkspaceItem.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="workspace-command-bar" aria-label="Workspace shortcuts">
|
||||||
|
<div className="workspace-nav-cluster">
|
||||||
|
{workspaceNavItems.slice(0, 5).map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.key}
|
||||||
|
type="button"
|
||||||
|
className={item.key === activeWorkspace ? 'command-chip command-chip-active' : 'command-chip'}
|
||||||
|
onClick={() => setActiveWorkspace(item.key)}
|
||||||
|
aria-pressed={item.key === activeWorkspace}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<span>{selectedProject ? `Active project: ${selectedProject.name}` : 'Start with a project or load the demo workflow.'}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{activeWorkspace === 'overview' ? (
|
{activeWorkspace === 'overview' ? (
|
||||||
<div className="workspace-stack">
|
<div className="workspace-stack">
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function DatasetPanel({
|
|||||||
const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length
|
const readyDatasets = datasets.filter((dataset) => dataset.status === 'ready').length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section data-testid="dataset-panel">
|
<section className="workspace-panel" data-testid="dataset-panel">
|
||||||
<div className="panel-title-row">
|
<div className="panel-title-row">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Data catalog</p>
|
<p className="eyebrow">Data catalog</p>
|
||||||
@@ -127,7 +127,12 @@ export function DatasetPanel({
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{loadingDatasets ? <p>Loading datasets...</p> : null}
|
{loadingDatasets ? <p>Loading datasets...</p> : null}
|
||||||
{datasets.length === 0 ? <p>No datasets yet</p> : null}
|
{datasets.length === 0 ? (
|
||||||
|
<div className="empty-state">
|
||||||
|
<strong>No datasets yet</strong>
|
||||||
|
<p>Upload vector, GeoJSON or raster data after selecting a project.</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
<ul className="dataset-list">
|
<ul className="dataset-list">
|
||||||
{datasets.map((dataset) => (
|
{datasets.map((dataset) => (
|
||||||
<li className={dataset.id === selectedDatasetId ? 'dataset-card dataset-card-active' : 'dataset-card'} key={dataset.id}>
|
<li className={dataset.id === selectedDatasetId ? 'dataset-card dataset-card-active' : 'dataset-card'} key={dataset.id}>
|
||||||
@@ -144,7 +149,7 @@ export function DatasetPanel({
|
|||||||
{dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'}
|
{dataset.status === 'ready' ? 'ready' : dataset.status === 'failed' ? 'failed' : 'pending'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="dataset-metrics">
|
<div className="dataset-metrics data-density-grid">
|
||||||
<span>size: {formatBytes(dataset.size_bytes)}</span>
|
<span>size: {formatBytes(dataset.size_bytes)}</span>
|
||||||
<span>features: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'n/a'}</span>
|
<span>features: {dataset.feature_count ?? dataset.vector_summary?.feature_count ?? 'n/a'}</span>
|
||||||
<span>bbox: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)}</span>
|
<span>bbox: {formatBounds(dataset.bounds_json ?? dataset.vector_summary?.bounds_json)}</span>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function DetectionLab({
|
|||||||
onRunQa,
|
onRunQa,
|
||||||
}: DetectionLabProps): JSX.Element {
|
}: DetectionLabProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="workspace-panel">
|
||||||
<div className="panel-title-row">
|
<div className="panel-title-row">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Object detection</p>
|
<p className="eyebrow">Object detection</p>
|
||||||
@@ -166,7 +166,7 @@ export function DetectionLab({
|
|||||||
</div>
|
</div>
|
||||||
{detectionRunError ? <p className="error">{detectionRunError}</p> : null}
|
{detectionRunError ? <p className="error">{detectionRunError}</p> : null}
|
||||||
{detectionRunResult ? (
|
{detectionRunResult ? (
|
||||||
<div>
|
<div className="result-summary-card">
|
||||||
<p>Status: {detectionRunResult.status}</p>
|
<p>Status: {detectionRunResult.status}</p>
|
||||||
<p>Message: {detectionRunResult.message}</p>
|
<p>Message: {detectionRunResult.message}</p>
|
||||||
<p>Analysis run: {detectionRunResult.analysis_run_id}</p>
|
<p>Analysis run: {detectionRunResult.analysis_run_id}</p>
|
||||||
@@ -221,6 +221,7 @@ export function DetectionLab({
|
|||||||
{loadingDetectionResults ? <p>Loading detection results...</p> : null}
|
{loadingDetectionResults ? <p>Loading detection results...</p> : null}
|
||||||
<p>Detections loaded: {detectionItems.length}</p>
|
<p>Detections loaded: {detectionItems.length}</p>
|
||||||
{detectionItems.length > 0 ? (
|
{detectionItems.length > 0 ? (
|
||||||
|
<div className="table-scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -241,6 +242,7 @@ export function DetectionLab({
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="lab-block">
|
<div className="lab-block">
|
||||||
@@ -261,7 +263,7 @@ export function DetectionLab({
|
|||||||
</button>
|
</button>
|
||||||
{detectionQaError ? <p className="error">{detectionQaError}</p> : null}
|
{detectionQaError ? <p className="error">{detectionQaError}</p> : null}
|
||||||
{detectionQaResult ? (
|
{detectionQaResult ? (
|
||||||
<div>
|
<div className="result-summary-card">
|
||||||
<p>Status: {detectionQaResult.status}</p>
|
<p>Status: {detectionQaResult.status}</p>
|
||||||
<p>Quality check: {detectionQaResult.quality_check_id}</p>
|
<p>Quality check: {detectionQaResult.quality_check_id}</p>
|
||||||
<p>Precision: {detectionQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
|
<p>Precision: {detectionQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function ProjectPanel({
|
|||||||
onLoadDemoWorkflow,
|
onLoadDemoWorkflow,
|
||||||
}: ProjectPanelProps): JSX.Element {
|
}: ProjectPanelProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<section data-testid="project-panel">
|
<section className="workspace-panel" data-testid="project-panel">
|
||||||
<div className="panel-title-row">
|
<div className="panel-title-row">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Context</p>
|
<p className="eyebrow">Context</p>
|
||||||
@@ -89,7 +89,12 @@ export function ProjectPanel({
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{projects.length === 0 ? <li>No projects yet</li> : null}
|
{projects.length === 0 ? (
|
||||||
|
<li className="empty-state">
|
||||||
|
<strong>No projects yet</strong>
|
||||||
|
<p>Create a project or load the offline demo workflow to populate the workbench.</p>
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function SegmentationLab({
|
|||||||
onRunQa,
|
onRunQa,
|
||||||
}: SegmentationLabProps): JSX.Element {
|
}: SegmentationLabProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section className="workspace-panel">
|
||||||
<div className="panel-title-row">
|
<div className="panel-title-row">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Polygon segmentation</p>
|
<p className="eyebrow">Polygon segmentation</p>
|
||||||
@@ -163,7 +163,7 @@ export function SegmentationLab({
|
|||||||
) : null}
|
) : null}
|
||||||
{segmentationRunError ? <p className="error">{segmentationRunError}</p> : null}
|
{segmentationRunError ? <p className="error">{segmentationRunError}</p> : null}
|
||||||
{segmentationRunResult ? (
|
{segmentationRunResult ? (
|
||||||
<div>
|
<div className="result-summary-card">
|
||||||
<p>Status: {segmentationRunResult.status}</p>
|
<p>Status: {segmentationRunResult.status}</p>
|
||||||
<p>Message: {segmentationRunResult.message}</p>
|
<p>Message: {segmentationRunResult.message}</p>
|
||||||
<p>Analysis run: {segmentationRunResult.analysis_run_id}</p>
|
<p>Analysis run: {segmentationRunResult.analysis_run_id}</p>
|
||||||
@@ -218,6 +218,7 @@ export function SegmentationLab({
|
|||||||
{loadingSegmentationResults ? <p>Loading segmentation results...</p> : null}
|
{loadingSegmentationResults ? <p>Loading segmentation results...</p> : null}
|
||||||
<p>Segmentations loaded: {segmentationItems.length}</p>
|
<p>Segmentations loaded: {segmentationItems.length}</p>
|
||||||
{segmentationItems.length > 0 ? (
|
{segmentationItems.length > 0 ? (
|
||||||
|
<div className="table-scroll">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -242,6 +243,7 @@ export function SegmentationLab({
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="lab-block">
|
<div className="lab-block">
|
||||||
@@ -262,7 +264,7 @@ export function SegmentationLab({
|
|||||||
</button>
|
</button>
|
||||||
{segmentationQaError ? <p className="error">{segmentationQaError}</p> : null}
|
{segmentationQaError ? <p className="error">{segmentationQaError}</p> : null}
|
||||||
{segmentationQaResult ? (
|
{segmentationQaResult ? (
|
||||||
<div>
|
<div className="result-summary-card">
|
||||||
<p>Status: {segmentationQaResult.status}</p>
|
<p>Status: {segmentationQaResult.status}</p>
|
||||||
<p>Quality check: {segmentationQaResult.quality_check_id}</p>
|
<p>Quality check: {segmentationQaResult.quality_check_id}</p>
|
||||||
<p>Precision: {segmentationQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
|
<p>Precision: {segmentationQaResult.precision?.toFixed(3) ?? 'n/a'}</p>
|
||||||
|
|||||||
+152
-17
@@ -4,6 +4,8 @@
|
|||||||
--bg: #eef4f1;
|
--bg: #eef4f1;
|
||||||
--panel: #ffffff;
|
--panel: #ffffff;
|
||||||
--panel-soft: #f8fbf9;
|
--panel-soft: #f8fbf9;
|
||||||
|
--surface-raised: #ffffff;
|
||||||
|
--surface-sunken: #f3f8f5;
|
||||||
--text: #132018;
|
--text: #132018;
|
||||||
--muted: #5f6f67;
|
--muted: #5f6f67;
|
||||||
--line: #cbd8d0;
|
--line: #cbd8d0;
|
||||||
@@ -14,6 +16,7 @@
|
|||||||
--warning: #b45309;
|
--warning: #b45309;
|
||||||
--danger: #991b1b;
|
--danger: #991b1b;
|
||||||
--shadow: 0 12px 32px rgba(33, 48, 41, 0.08);
|
--shadow: 0 12px 32px rgba(33, 48, 41, 0.08);
|
||||||
|
--shadow-soft: 0 8px 24px rgba(33, 48, 41, 0.06);
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,6 +35,8 @@ body {
|
|||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(15, 118, 110, 0.08), rgba(238, 244, 241, 0) 18rem),
|
linear-gradient(180deg, rgba(15, 118, 110, 0.08), rgba(238, 244, 241, 0) 18rem),
|
||||||
var(--bg);
|
var(--bg);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
@@ -183,8 +188,8 @@ section {
|
|||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: rgba(255, 255, 255, 0.94);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-grid {
|
.workspace-grid {
|
||||||
@@ -519,12 +524,12 @@ section li strong + div {
|
|||||||
top: 0;
|
top: 0;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(12rem, 18rem) minmax(0, 1fr);
|
grid-template-columns: minmax(12rem, 17rem) minmax(0, 1fr);
|
||||||
gap: 0.75rem;
|
gap: 0.9rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid var(--line);
|
border-bottom: 1px solid var(--line);
|
||||||
padding: 0.55rem 0.85rem;
|
padding: 0.58rem 1rem;
|
||||||
background: rgba(248, 251, 249, 0.96);
|
background: rgba(250, 253, 251, 0.97);
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,11 +550,11 @@ section li strong + div {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-bar > div {
|
.context-bar > div {
|
||||||
min-height: 3.2rem;
|
min-height: 3rem;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 8px;
|
border-radius: 7px;
|
||||||
padding: 0.44rem 0.58rem;
|
padding: 0.44rem 0.58rem;
|
||||||
background: #ffffff;
|
background: var(--surface-raised);
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-bar span {
|
.context-bar span {
|
||||||
@@ -581,7 +586,7 @@ section li strong + div {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-color: var(--line);
|
border-color: var(--line);
|
||||||
background: #f8fbf9;
|
background: var(--surface-sunken);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workbench-sidebar {
|
.workbench-sidebar {
|
||||||
@@ -636,7 +641,7 @@ section li strong + div {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.85rem 1rem 1rem;
|
padding: 0.9rem 1.05rem 1.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workbench-inspector {
|
.workbench-inspector {
|
||||||
@@ -656,11 +661,12 @@ section li strong + div {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 0.85rem;
|
margin-bottom: 0.65rem;
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 8px;
|
border-radius: 7px;
|
||||||
padding: 0.72rem 0.85rem;
|
padding: 0.76rem 0.9rem;
|
||||||
background: #ffffff;
|
background: var(--surface-raised);
|
||||||
|
box-shadow: var(--shadow-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-heading h2 {
|
.workspace-heading h2 {
|
||||||
@@ -674,6 +680,50 @@ section li strong + div {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace-command-bar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 0.8rem;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.85rem;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 0.48rem;
|
||||||
|
background: rgba(255, 255, 255, 0.74);
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-command-bar > span {
|
||||||
|
max-width: 24rem;
|
||||||
|
padding-right: 0.35rem;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-nav-cluster {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-chip {
|
||||||
|
width: auto;
|
||||||
|
min-height: 2.05rem;
|
||||||
|
border-color: transparent;
|
||||||
|
padding: 0.32rem 0.62rem;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-chip-active {
|
||||||
|
border-color: rgba(15, 118, 110, 0.34);
|
||||||
|
background: var(--accent-soft);
|
||||||
|
color: var(--accent-strong);
|
||||||
|
}
|
||||||
|
|
||||||
.workspace-stack {
|
.workspace-stack {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
@@ -706,6 +756,11 @@ section li strong + div {
|
|||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace-panel {
|
||||||
|
border-radius: 7px;
|
||||||
|
background: var(--surface-raised);
|
||||||
|
}
|
||||||
|
|
||||||
.workspace-grid-data > section:nth-child(3) {
|
.workspace-grid-data > section:nth-child(3) {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
@@ -768,6 +823,28 @@ section th {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-scroll {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 22rem;
|
||||||
|
overflow: auto;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 7px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll table {
|
||||||
|
min-width: 42rem;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll thead th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: #f8fbf9;
|
||||||
|
}
|
||||||
|
|
||||||
.workbench-inspector .job-result,
|
.workbench-inspector .job-result,
|
||||||
.workbench-inspector pre {
|
.workbench-inspector pre {
|
||||||
max-height: 16rem;
|
max-height: 16rem;
|
||||||
@@ -854,6 +931,13 @@ section th {
|
|||||||
background: var(--panel-soft);
|
background: var(--panel-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-density-grid {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 0.55rem;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.entity-list,
|
.entity-list,
|
||||||
.dataset-list,
|
.dataset-list,
|
||||||
.model-list,
|
.model-list,
|
||||||
@@ -922,6 +1006,14 @@ button.entity-card {
|
|||||||
padding: 0.85rem;
|
padding: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dataset-card,
|
||||||
|
.model-card,
|
||||||
|
.quality-check-card,
|
||||||
|
.export-card,
|
||||||
|
.lab-block {
|
||||||
|
box-shadow: 0 1px 0 rgba(19, 32, 24, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
.dataset-card-active {
|
.dataset-card-active {
|
||||||
border-color: rgba(15, 118, 110, 0.5);
|
border-color: rgba(15, 118, 110, 0.5);
|
||||||
box-shadow: inset 4px 0 0 var(--accent);
|
box-shadow: inset 4px 0 0 var(--accent);
|
||||||
@@ -974,6 +1066,24 @@ button.entity-card {
|
|||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.result-summary-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||||
|
gap: 0.45rem 0.7rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
border: 1px solid rgba(15, 118, 110, 0.22);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 0.72rem;
|
||||||
|
background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-summary-card p {
|
||||||
|
margin: 0;
|
||||||
|
color: #23423b;
|
||||||
|
font-size: 0.86rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
.quality-summary-grid {
|
.quality-summary-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
@@ -1056,6 +1166,7 @@ button.entity-card {
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state p,
|
.empty-state p,
|
||||||
@@ -1379,11 +1490,13 @@ button.entity-card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.workspace-heading,
|
.workspace-heading,
|
||||||
|
.workspace-command-bar,
|
||||||
.status-strip-header {
|
.status-strip-header {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-heading p:last-child,
|
.workspace-heading p:last-child,
|
||||||
|
.workspace-command-bar > span,
|
||||||
.status-next-action {
|
.status-next-action {
|
||||||
margin-top: 0.45rem;
|
margin-top: 0.45rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -1419,11 +1532,33 @@ button.entity-card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-bar,
|
.context-bar,
|
||||||
.workbench-sidebar nav,
|
.quick-action-grid,
|
||||||
.quick-action-grid {
|
.workspace-nav-cluster {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workbench-sidebar nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workbench-sidebar .nav-item {
|
||||||
|
min-width: 8.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-nav-cluster {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-chip {
|
||||||
|
min-width: 6.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.status-strip-grid {
|
.status-strip-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user