Polish workbench shell density
This commit is contained in:
@@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Sprint 82 Shell density polish (2026-06-20)
|
||||||
|
|
||||||
|
- Added a keyboard skip link to jump directly from the workbench shell to active workspace content.
|
||||||
|
- Added an explicit primary workspace navigation label and main focus target.
|
||||||
|
- Made narrow-view context chips, sidebar navigation and workspace shortcuts more compact and scroll-safe.
|
||||||
|
- Added static regression coverage for shell density, skip-link and mobile navigation contracts.
|
||||||
|
- No API contracts, migrations, backend behavior, provider fetching or AI model behavior changed.
|
||||||
|
|
||||||
## Sprint 81 Result state consistency polish (2026-06-20)
|
## Sprint 81 Result state consistency polish (2026-06-20)
|
||||||
|
|
||||||
- Added shared result-state styling for compact loading, error, empty and ready states.
|
- Added shared result-state styling for compact loading, error, empty and ready states.
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_workbench_shell_has_skip_link_and_main_focus_target() -> None:
|
||||||
|
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert 'className="skip-link"' in app
|
||||||
|
assert 'href="#workspace-main"' in app
|
||||||
|
assert 'nav aria-label="Primary workspaces"' in app
|
||||||
|
assert 'id="workspace-main"' in app
|
||||||
|
assert "tabIndex={-1}" in app
|
||||||
|
|
||||||
|
|
||||||
|
def test_mobile_shell_density_contracts_are_defined() -> None:
|
||||||
|
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert ".skip-link" in css
|
||||||
|
assert ".skip-link:focus-visible" in css
|
||||||
|
assert "scroll-margin-top: 8.5rem;" in css
|
||||||
|
assert "grid-template-columns: repeat(4, minmax(6.75rem, 1fr));" in css
|
||||||
|
assert "overflow-x: auto;" in css
|
||||||
|
assert "scroll-snap-type: x proximity;" in css
|
||||||
|
assert "white-space: nowrap;" in css
|
||||||
|
|
||||||
|
|
||||||
|
def test_small_screen_navigation_keeps_labels_readable() -> None:
|
||||||
|
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "@media (max-width: 620px)" in css
|
||||||
|
assert ".workbench-sidebar .nav-item" in css
|
||||||
|
assert "min-width: 9.5rem;" in css
|
||||||
|
assert ".nav-item small" in css
|
||||||
|
assert "line-height: 1.18;" in css
|
||||||
@@ -2984,3 +2984,30 @@ Limitations:
|
|||||||
|
|
||||||
Next recommended pass:
|
Next recommended pass:
|
||||||
- Continue with visual density review for topbar/sidebar/responsive shell after another live browser pass.
|
- Continue with visual density review for topbar/sidebar/responsive shell after another live browser pass.
|
||||||
|
|
||||||
|
## Sprint 82 Shell density polish (2026-06-20)
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- Added a keyboard skip link that targets the active workspace main region.
|
||||||
|
- Added an explicit `Primary workspaces` label to the sidebar navigation and a focusable `workspace-main` target.
|
||||||
|
- Tightened narrow-screen topbar, context chip, sidebar nav and workspace shortcut density while keeping intentional horizontal rails scroll-safe.
|
||||||
|
- Added `backend/tests/test_sprint82_shell_density_polish.py`.
|
||||||
|
- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
||||||
|
|
||||||
|
Tested:
|
||||||
|
- Live browser pre-check against `http://192.168.10.150:1202` showed no console errors, no horizontal document overflow and a too-tall narrow viewport topbar/context stack.
|
||||||
|
- Red step: `python -m pytest backend/tests/test_sprint82_shell_density_polish.py -q` failed on missing skip-link, main focus target and compact mobile shell CSS contracts.
|
||||||
|
- `python -m pytest backend/tests/test_sprint82_shell_density_polish.py -q` (`3 passed`)
|
||||||
|
- `python -m pytest backend/tests/test_sprint82_shell_density_polish.py backend/tests/test_sprint79_accessibility_focus_polish.py backend/tests/test_sprint72_mobile_overflow_hardening.py backend/tests/test_sprint49_workbench_shell_refactor.py backend/tests/test_sprint47_workbench_interaction_smoke.py -q` (`13 passed`)
|
||||||
|
- `cd frontend && npm run typecheck`
|
||||||
|
- `cd frontend && npm run build`
|
||||||
|
- `bash scripts/run_readiness_check.sh` (`279 passed`; frontend typecheck/build passed; Alembic single head `202606120900`)
|
||||||
|
|
||||||
|
Open:
|
||||||
|
- Deployment and live browser verification still need to run for this pass.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- Frontend shell presentation/accessibility polish only; no workflow behavior, API contract, persistence, migration, provider fetching or AI/model changes.
|
||||||
|
|
||||||
|
Next recommended pass:
|
||||||
|
- Continue with a live visual review of dense workspace panel hierarchy after the shell density changes are deployed.
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [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.
|
- [x] Add compact loading/error/empty/result state polish across QA, exports and AI labs.
|
||||||
|
- [x] Add compact shell density polish for topbar context, mobile navigation and workspace skip flow.
|
||||||
|
|
||||||
## Sprint 8 status
|
## Sprint 8 status
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ QA/QC, exports and AI lab result panels use shared loading, error, empty and rea
|
|||||||
- 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.
|
- 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.
|
||||||
|
- The shell includes a keyboard skip link, an explicit primary workspace navigation label and compact horizontal context/navigation rails on narrow screens so active workspace content is reachable without a tall preamble.
|
||||||
- The Export Center includes a handoff readiness summary, grouped artifact actions and provenance-rich export cards so report/GeoJSON handoff stays understandable in long-running demo projects.
|
- The Export Center includes a handoff readiness summary, grouped artifact actions and provenance-rich export cards so report/GeoJSON handoff stays understandable in long-running demo projects.
|
||||||
|
|
||||||
## Workbench shell refactor
|
## Workbench shell refactor
|
||||||
|
|||||||
@@ -452,6 +452,9 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell workbench-shell">
|
<div className="app-shell workbench-shell">
|
||||||
|
<a className="skip-link" href="#workspace-main">
|
||||||
|
Skip to workspace
|
||||||
|
</a>
|
||||||
<header className="workbench-topbar">
|
<header className="workbench-topbar">
|
||||||
<div className="brand-block">
|
<div className="brand-block">
|
||||||
<p className="eyebrow">GeoAI Workbench</p>
|
<p className="eyebrow">GeoAI Workbench</p>
|
||||||
@@ -481,7 +484,7 @@ function App(): JSX.Element {
|
|||||||
|
|
||||||
<div className="workbench-layout">
|
<div className="workbench-layout">
|
||||||
<aside className="workbench-sidebar" aria-label="Workbench navigation">
|
<aside className="workbench-sidebar" aria-label="Workbench navigation">
|
||||||
<nav>
|
<nav aria-label="Primary workspaces">
|
||||||
{workspaceNavItems.map((item) => (
|
{workspaceNavItems.map((item) => (
|
||||||
<button
|
<button
|
||||||
key={item.key}
|
key={item.key}
|
||||||
@@ -499,7 +502,7 @@ function App(): JSX.Element {
|
|||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main className="workbench-main">
|
<main className="workbench-main" id="workspace-main" tabIndex={-1}>
|
||||||
<div className="workspace-heading">
|
<div className="workspace-heading">
|
||||||
<div>
|
<div>
|
||||||
<p className="eyebrow">Workspace</p>
|
<p className="eyebrow">Workspace</p>
|
||||||
|
|||||||
@@ -48,6 +48,31 @@ body {
|
|||||||
overflow-x: clip;
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skip-link {
|
||||||
|
position: fixed;
|
||||||
|
top: 0.45rem;
|
||||||
|
left: 0.75rem;
|
||||||
|
z-index: 100;
|
||||||
|
transform: translateY(-150%);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.48rem 0.7rem;
|
||||||
|
background: #ffffff;
|
||||||
|
color: var(--accent-strong);
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-decoration: none;
|
||||||
|
box-shadow: var(--shadow-soft);
|
||||||
|
transition: transform 120ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link:focus-visible {
|
||||||
|
transform: translateY(0);
|
||||||
|
outline: 3px solid var(--focus-ring);
|
||||||
|
outline-offset: 2px;
|
||||||
|
box-shadow: 0 0 0 5px var(--focus-ring-soft);
|
||||||
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
input,
|
input,
|
||||||
select,
|
select,
|
||||||
@@ -671,6 +696,7 @@ section li strong + div {
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
scroll-margin-top: 8.5rem;
|
||||||
padding: 0.9rem 1.05rem 1.15rem;
|
padding: 0.9rem 1.05rem 1.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2325,11 +2351,36 @@ button.entity-card {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.workbench-sidebar nav,
|
.workbench-sidebar nav,
|
||||||
.context-bar,
|
|
||||||
.quick-action-grid {
|
.quick-action-grid {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.context-bar {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(6.75rem, 1fr));
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overscroll-behavior-x: contain;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
|
padding-bottom: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-bar > div {
|
||||||
|
min-height: 2.62rem;
|
||||||
|
padding: 0.36rem 0.48rem;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-bar span,
|
||||||
|
.context-bar strong {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-bar strong {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
min-height: 3.45rem;
|
min-height: 3.45rem;
|
||||||
}
|
}
|
||||||
@@ -2390,6 +2441,18 @@ button.entity-card {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workbench-topbar {
|
||||||
|
gap: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-block h1 {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-block .eyebrow {
|
||||||
|
font-size: 0.62rem;
|
||||||
|
}
|
||||||
|
|
||||||
.inspector-tabs {
|
.inspector-tabs {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -2414,11 +2477,19 @@ button.entity-card {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overscroll-behavior-x: contain;
|
overscroll-behavior-x: contain;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
padding-bottom: 0.2rem;
|
padding-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workbench-sidebar .nav-item {
|
.workbench-sidebar .nav-item {
|
||||||
min-width: 8.75rem;
|
min-width: 9.5rem;
|
||||||
|
min-height: 3.2rem;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item small {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
line-height: 1.18;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-nav-cluster {
|
.workspace-nav-cluster {
|
||||||
@@ -2427,11 +2498,13 @@ button.entity-card {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overscroll-behavior-x: contain;
|
overscroll-behavior-x: contain;
|
||||||
|
scroll-snap-type: x proximity;
|
||||||
padding-bottom: 0.1rem;
|
padding-bottom: 0.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.command-chip {
|
.command-chip {
|
||||||
min-width: 6.75rem;
|
min-width: 6.75rem;
|
||||||
|
scroll-snap-align: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-strip-grid {
|
.status-strip-grid {
|
||||||
|
|||||||
Reference in New Issue
Block a user