diff --git a/CHANGELOG.md b/CHANGELOG.md index fb74b906..4771d70c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ # Changelog +## Sprint 43 Workbench bootstrap hook decomposition (2026-06-17) + +- Moved frontend bootstrap/project/result reload effects from `App.tsx` into `frontend/src/hooks/useWorkbenchBootstrap.ts`. +- `App.tsx` no longer imports or owns `useEffect`; it wires hook state into panels and delegates lifecycle loading to focused hooks. +- Extended orchestration regression tests so bootstrap loading and result refresh effects stay out of `App.tsx`. +- No behavior, API contracts, migrations, provider fetching or AI behavior changed. + ## Sprint 42 App entrypoint cleanup (2026-06-17) - Removed the stale `FormEvent`/`useState` React imports from `frontend/src/App.tsx`. diff --git a/backend/tests/test_sprint39_frontend_orchestration_hooks.py b/backend/tests/test_sprint39_frontend_orchestration_hooks.py index 5f3cf03d..510cc0aa 100644 --- a/backend/tests/test_sprint39_frontend_orchestration_hooks.py +++ b/backend/tests/test_sprint39_frontend_orchestration_hooks.py @@ -14,11 +14,13 @@ def test_app_uses_shared_orchestration_hooks() -> None: assert "useProviderCapabilities" in app assert "useChangeDetectionWorkflow" in app assert "useMapWorkspaceState" in app + assert "useWorkbenchBootstrap" in app assert "from './hooks/useProjectWorkspace'" in app assert "from './hooks/useDemoWorkflow'" in app assert "from './hooks/useProviderCapabilities'" in app assert "from './hooks/useChangeDetectionWorkflow'" in app assert "from './hooks/useMapWorkspaceState'" in app + assert "from './hooks/useWorkbenchBootstrap'" in app assert "projectsApi" not in app assert "areasApi" not in app assert "datasetsApi" not in app @@ -33,8 +35,9 @@ def test_app_entrypoint_has_clean_encoding_and_react_imports() -> None: app = app_path.read_text(encoding="utf-8") assert not app_bytes.startswith(b"\xef\xbb\xbf") - assert "import { useEffect, useMemo } from 'react'" in app + assert "import { useMemo } from 'react'" in app assert "FormEvent" not in app + assert "useEffect" not in app assert "useState" not in app @@ -52,6 +55,24 @@ def test_demo_workflow_hook_owns_demo_api_and_cross_module_selection() -> None: assert "loadDatasetDetails(result.project_id, candidateDataset)" in hook +def test_workbench_bootstrap_hook_owns_entrypoint_effects() -> None: + app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8") + hook = (ROOT / "frontend" / "src" / "hooks" / "useWorkbenchBootstrap.ts").read_text(encoding="utf-8") + + assert "loadProjects().catch(() => null)" not in app + assert "loadDetectionResults().catch(() => null)" not in app + assert "loadSegmentationResults().catch(() => null)" not in app + assert "loadProjects().catch(() => null)" in hook + assert "loadCapabilities().catch(() => null)" in hook + assert "loadDetectionModels().catch(() => null)" in hook + assert "loadSegmentationModels().catch(() => null)" in hook + assert "resetProjectData()" in hook + assert "resetDatasetForProject()" in hook + assert "loadProjectData(selectedProjectId).catch(() => null)" in hook + assert "loadDetectionResults().catch(() => null)" in hook + assert "loadSegmentationResults().catch(() => null)" in hook + + def test_project_workspace_hook_owns_project_area_dataset_loading() -> None: hook = (ROOT / "frontend" / "src" / "hooks" / "useProjectWorkspace.ts").read_text(encoding="utf-8") diff --git a/docs/CODEX_EXECUTION_LOG.md b/docs/CODEX_EXECUTION_LOG.md index cb452e4b..edd08567 100644 --- a/docs/CODEX_EXECUTION_LOG.md +++ b/docs/CODEX_EXECUTION_LOG.md @@ -1,3 +1,33 @@ +## Sprint 43 Workbench bootstrap hook decomposition (2026-06-17) + +Changed: +- Moved frontend bootstrap, project-change reload/reset and detection/segmentation result reload effects from `frontend/src/App.tsx` into `frontend/src/hooks/useWorkbenchBootstrap.ts`. +- Kept `App.tsx` as a composition root that wires hook outputs into panels; it no longer imports `useEffect`. +- Extended orchestration tests so lifecycle side effects stay in the bootstrap hook. +- Updated frontend README, changelog and TODO status. + +Validation: +- `cd backend && python -m pytest tests/test_sprint39_frontend_orchestration_hooks.py -q` passed: 9 tests. +- `python -m compileall backend/app` passed. +- `cd backend && python -m pytest -W error::DeprecationWarning` passed: 193 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. +- `App.tsx` size audit after extraction: 621 lines; `useWorkbenchBootstrap.ts`: 81 lines. + +Open: +- Tower deployment and runtime smoke should run before considering this pass deployed. + +Limitations: +- No UX behavior, API contracts, migrations, provider fetching or AI behavior changed. +- `App.tsx` line count remains high because it explicitly wires many panel props; the remaining size is primarily composition. + +Next recommended pass: +- Pick the next V1 stabilization focus: UI browser regression coverage, backend service contract audit, or golden dataset expansion. + ## Sprint 42 App entrypoint cleanup (2026-06-17) Changed: diff --git a/docs/TODO.md b/docs/TODO.md index 244b34a2..e6326658 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -55,7 +55,8 @@ This file now starts with the current implementation status. Older preparation/b - [x] Project/area/dataset cross-load orchestration hook decomposition. - [x] Demo workflow orchestration hook decomposition. - [x] Final `App.tsx` import/encoding cleanup and size audit. -- [ ] Optional final bootstrap-effect extraction if `App.tsx` orchestration needs another size reduction. +- [x] Optional final bootstrap-effect extraction. +- [ ] Decide next V1 stabilization focus: UI browser regression coverage, backend service contract audit, or golden dataset expansion. ## Sprint 8 status diff --git a/frontend/README.md b/frontend/README.md index 7d833039..6a6d7e02 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -216,6 +216,7 @@ React + TypeScript + MapLibre foundation for project/area/dataset workflow. - Project, area and dataset-list loading lives in `src/hooks/useProjectWorkspace.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. +- Workbench bootstrap and reload effects live in `src/hooks/useWorkbenchBootstrap.ts`, keeping `App.tsx` focused on composing hooks into panels. ## Raster dependency visibility diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cb8c57d3..442a1ac0 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from 'react' +import { useMemo } from 'react' import './styles/app.css' import { ChangeDetectionPanel } from './components/analysis/ChangeDetectionPanel' import { DatasetDetailPanel } from './components/datasets/DatasetDetailPanel' @@ -24,6 +24,7 @@ import { useProviderCapabilities } from './hooks/useProviderCapabilities' import { useProjectWorkspace } from './hooks/useProjectWorkspace' import { useQualityWorkflow } from './hooks/useQualityWorkflow' import { useSegmentationWorkflow } from './hooks/useSegmentationWorkflow' +import { useWorkbenchBootstrap } from './hooks/useWorkbenchBootstrap' function isVectorDatasetType(datasetType: string): boolean { return datasetType === 'vector' || datasetType === 'geojson' @@ -343,36 +344,31 @@ function App(): JSX.Element { setErrorMessage, }) - useEffect(() => { - loadProjects().catch(() => null) - loadCapabilities().catch(() => null) - loadDetectionModels().catch(() => null) - loadSegmentationModels().catch(() => null) - }, []) - - useEffect(() => { - if (!selectedProjectId) { - resetProjectData() - resetDatasetForProject() - resetDetectionForProject() - resetSegmentationForProject() - resetExportsForProject() - return - } - loadProjectData(selectedProjectId).catch(() => null) - loadDetectionRuns(selectedProjectId).catch(() => null) - loadSegmentationRuns(selectedProjectId).catch(() => null) - loadQualityChecks(selectedProjectId).catch(() => null) - loadExports(selectedProjectId).catch(() => null) - }, [selectedProjectId]) - - useEffect(() => { - loadDetectionResults().catch(() => null) - }, [selectedDetectionRunId, detectionClassFilter, detectionMinConfidenceFilter]) - - useEffect(() => { - loadSegmentationResults().catch(() => null) - }, [selectedSegmentationRunId, segmentationClassFilter, segmentationMinConfidenceFilter]) + useWorkbenchBootstrap({ + selectedProjectId, + selectedDetectionRunId, + detectionClassFilter, + detectionMinConfidenceFilter, + selectedSegmentationRunId, + segmentationClassFilter, + segmentationMinConfidenceFilter, + loadProjects, + loadCapabilities, + loadDetectionModels, + loadSegmentationModels, + loadProjectData, + loadDetectionRuns, + loadSegmentationRuns, + loadQualityChecks, + loadExports, + loadDetectionResults, + loadSegmentationResults, + resetProjectData, + resetDatasetForProject, + resetDetectionForProject, + resetSegmentationForProject, + resetExportsForProject, + }) return (