Polish default demo project selection
This commit is contained in:
@@ -7,6 +7,14 @@
|
||||
|
||||
# Changelog
|
||||
|
||||
## Sprint 45 Default demo selection polish (2026-06-17)
|
||||
|
||||
- Improved frontend project selection so a cold start prefers a populated demo/workbench project over an empty first project.
|
||||
- Preserved the current project selection when it still exists and selected newly created projects immediately after creation.
|
||||
- Updated the demo workflow hook to pass the seeded project as the preferred project during refresh.
|
||||
- Added static regression coverage for the smarter project selection and demo refresh behavior.
|
||||
- No API contracts, migrations, provider fetching, AI behavior or product capabilities changed.
|
||||
|
||||
## Sprint 44 Workbench UI polish pass (2026-06-17)
|
||||
|
||||
- Reworked the frontend workbench styling into a cleaner operational GIS interface with compact panels, modern controls, restrained green/neutral accents and scroll-contained long sections.
|
||||
|
||||
@@ -46,6 +46,7 @@ def test_demo_workflow_hook_owns_demo_api_and_cross_module_selection() -> None:
|
||||
|
||||
assert "demoApi.seedWorkflow" in hook
|
||||
assert "loadDemoWorkflow" in hook
|
||||
assert "loadProjects(result.project_id)" in hook
|
||||
assert "setSelectedProjectId(result.project_id)" in hook
|
||||
assert "setSelectedDatasetId(result.candidate_dataset_id)" in hook
|
||||
assert "setSelectedMapAreaId(result.area_id)" in hook
|
||||
@@ -76,8 +77,13 @@ def test_workbench_bootstrap_hook_owns_entrypoint_effects() -> None:
|
||||
def test_project_workspace_hook_owns_project_area_dataset_loading() -> None:
|
||||
hook = (ROOT / "frontend" / "src" / "hooks" / "useProjectWorkspace.ts").read_text(encoding="utf-8")
|
||||
|
||||
assert "DEMO_PROJECT_NAME = 'GeoIntel Demo - Building QA'" in hook
|
||||
assert "pickInitialProjectId" in hook
|
||||
assert "preferredProjectId" in hook
|
||||
assert "data.areas.length > 0 && data.datasets.length > 0" in hook
|
||||
assert "projectsApi.list" in hook
|
||||
assert "projectsApi.create" in hook
|
||||
assert "setSelectedProjectId(createdProject.id)" in hook
|
||||
assert "areasApi.list" in hook
|
||||
assert "areasApi.create" in hook
|
||||
assert "datasetsApi.list" in hook
|
||||
|
||||
@@ -1,3 +1,33 @@
|
||||
## Sprint 45 Default demo selection polish (2026-06-17)
|
||||
|
||||
Changed:
|
||||
- Updated `frontend/src/hooks/useProjectWorkspace.ts` so cold-start project selection preserves an existing selected project, honors an explicit preferred project and otherwise prefers a populated demo/workbench project over an empty first project.
|
||||
- New project creation now selects the newly created project immediately after creation.
|
||||
- Updated `frontend/src/hooks/useDemoWorkflow.ts` so demo seed refresh passes the seeded project id to `loadProjects`.
|
||||
- Extended orchestration tests to cover preferred demo selection and project creation selection behavior.
|
||||
|
||||
Validation:
|
||||
- `cd backend && python -m pytest tests/test_sprint39_frontend_orchestration_hooks.py tests/test_sprint21_demo_workflow_smoke.py -q` passed: 11 tests.
|
||||
- `python -m compileall backend/app` passed.
|
||||
- `cd backend && python -m pytest -W error::DeprecationWarning` passed: 194 tests.
|
||||
- `bash scripts/run_readiness_check.sh` passed.
|
||||
- `cd frontend && npm run typecheck` passed.
|
||||
- `cd frontend && npm run build` passed.
|
||||
- `cd backend && python -m alembic heads` passed: `202606120900 (head)`.
|
||||
- `cd backend && python -m alembic upgrade head --sql` passed.
|
||||
- `bash -n scripts/live_migration_smoke.sh` passed.
|
||||
- Browser check against local Vite preview with live backend passed after async settle: selected project `GeoIntel Demo - Building QA`, status showed `1 area`, `2/2 ready` datasets and `2 checks`.
|
||||
|
||||
Open:
|
||||
- Tower deployment and live smoke should run before considering this pass deployed.
|
||||
|
||||
Limitations:
|
||||
- No API contracts, migrations, provider fetching, AI behavior or product capabilities changed.
|
||||
- The heuristic probes up to eight project candidates on cold start; a future backend list endpoint with area/dataset counts would make this cleaner.
|
||||
|
||||
Next recommended pass:
|
||||
- Add a lightweight browser regression script for the demo start page once the UI state is stable enough to automate end to end.
|
||||
|
||||
## Sprint 44 Workbench UI polish pass (2026-06-17)
|
||||
|
||||
Changed:
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { DatasetCreateResponse, QualityCheckRead } from '../types'
|
||||
import { formatError } from '../lib/formatError'
|
||||
|
||||
interface DemoWorkflowOptions {
|
||||
loadProjects: () => Promise<void>
|
||||
loadProjects: (preferredProjectId?: string | null) => Promise<void>
|
||||
loadProjectData: (projectId: string) => Promise<{ datasets: DatasetCreateResponse[] } | null>
|
||||
loadDatasetDetails: (projectId: string, dataset: DatasetCreateResponse) => Promise<void>
|
||||
loadDetectionRuns: (projectId?: string | null) => Promise<void>
|
||||
@@ -58,7 +58,7 @@ export function useDemoWorkflow({
|
||||
setDetectionReferenceDatasetId(result.reference_dataset_id)
|
||||
setSegmentationReferenceDatasetId(result.reference_dataset_id)
|
||||
setDemoWorkflowMessage(result.message)
|
||||
await loadProjects()
|
||||
await loadProjects(result.project_id)
|
||||
const [projectData] = await Promise.all([
|
||||
loadProjectData(result.project_id),
|
||||
loadDetectionRuns(result.project_id),
|
||||
|
||||
@@ -12,6 +12,12 @@ import type {
|
||||
ProjectRead,
|
||||
} from '../types'
|
||||
|
||||
const DEMO_PROJECT_NAME = 'GeoIntel Demo - Building QA'
|
||||
|
||||
function isDemoProject(project: ProjectRead): boolean {
|
||||
return project.name === DEMO_PROJECT_NAME
|
||||
}
|
||||
|
||||
export function useProjectWorkspace() {
|
||||
const [projects, setProjects] = useState<ProjectRead[]>([])
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null)
|
||||
@@ -39,14 +45,45 @@ export function useProjectWorkspace() {
|
||||
[projects, selectedProjectId],
|
||||
)
|
||||
|
||||
const loadProjects = async () => {
|
||||
const fetchProjectData = async (projectId: string): Promise<{ areas: AreaRead[]; datasets: DatasetCreateResponse[] }> => {
|
||||
const [areaResponse, datasetResponse]: [AreaListResponse, DatasetListResponse] = await Promise.all([
|
||||
areasApi.list(projectId),
|
||||
datasetsApi.list(projectId),
|
||||
])
|
||||
return { areas: areaResponse.items, datasets: datasetResponse.items }
|
||||
}
|
||||
|
||||
const pickInitialProjectId = async (items: ProjectRead[], preferredProjectId?: string | null) => {
|
||||
if (preferredProjectId && items.some((project) => project.id === preferredProjectId)) {
|
||||
return preferredProjectId
|
||||
}
|
||||
if (selectedProjectId && items.some((project) => project.id === selectedProjectId)) {
|
||||
return selectedProjectId
|
||||
}
|
||||
const demoProjects = items.filter(isDemoProject)
|
||||
const candidates = [...demoProjects, ...items.filter((project) => !isDemoProject(project))].slice(0, 8)
|
||||
for (const project of candidates) {
|
||||
try {
|
||||
const data = await fetchProjectData(project.id)
|
||||
if (data.areas.length > 0 && data.datasets.length > 0) {
|
||||
return project.id
|
||||
}
|
||||
} catch {
|
||||
// Project list should still render if a candidate's detail endpoints are temporarily unavailable.
|
||||
}
|
||||
}
|
||||
return demoProjects[0]?.id ?? items[0]?.id ?? null
|
||||
}
|
||||
|
||||
const loadProjects = async (preferredProjectId?: string | null) => {
|
||||
setLoadingProjects(true)
|
||||
setErrorMessage(null)
|
||||
try {
|
||||
const response = await projectsApi.list()
|
||||
setProjects(response.items)
|
||||
if (!selectedProjectId && response.items.length > 0) {
|
||||
setSelectedProjectId(response.items[0].id)
|
||||
const nextProjectId = await pickInitialProjectId(response.items, preferredProjectId)
|
||||
if (nextProjectId && nextProjectId !== selectedProjectId) {
|
||||
setSelectedProjectId(nextProjectId)
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Failed to load projects')
|
||||
@@ -60,13 +97,10 @@ export function useProjectWorkspace() {
|
||||
setLoadingDatasets(true)
|
||||
setErrorMessage(null)
|
||||
try {
|
||||
const [areaResponse, datasetResponse]: [AreaListResponse, DatasetListResponse] = await Promise.all([
|
||||
areasApi.list(projectId),
|
||||
datasetsApi.list(projectId),
|
||||
])
|
||||
setAreas(areaResponse.items)
|
||||
setDatasets(datasetResponse.items)
|
||||
return { areas: areaResponse.items, datasets: datasetResponse.items }
|
||||
const projectData = await fetchProjectData(projectId)
|
||||
setAreas(projectData.areas)
|
||||
setDatasets(projectData.datasets)
|
||||
return projectData
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Failed to load project data')
|
||||
return null
|
||||
@@ -83,13 +117,14 @@ export function useProjectWorkspace() {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await projectsApi.create({
|
||||
const createdProject = await projectsApi.create({
|
||||
name: projectForm.name.trim(),
|
||||
description: projectForm.description?.trim() || undefined,
|
||||
region: projectForm.region?.trim() || 'Kempen',
|
||||
})
|
||||
setProjectForm((previous) => ({ ...previous, name: '', description: '' }))
|
||||
await loadProjects()
|
||||
setSelectedProjectId(createdProject.id)
|
||||
await loadProjects(createdProject.id)
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : 'Failed to create project')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user