Extract workbench bootstrap hook
This commit is contained in:
@@ -7,6 +7,13 @@
|
|||||||
|
|
||||||
# Changelog
|
# 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)
|
## Sprint 42 App entrypoint cleanup (2026-06-17)
|
||||||
|
|
||||||
- Removed the stale `FormEvent`/`useState` React imports from `frontend/src/App.tsx`.
|
- Removed the stale `FormEvent`/`useState` React imports from `frontend/src/App.tsx`.
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ def test_app_uses_shared_orchestration_hooks() -> None:
|
|||||||
assert "useProviderCapabilities" in app
|
assert "useProviderCapabilities" in app
|
||||||
assert "useChangeDetectionWorkflow" in app
|
assert "useChangeDetectionWorkflow" in app
|
||||||
assert "useMapWorkspaceState" in app
|
assert "useMapWorkspaceState" in app
|
||||||
|
assert "useWorkbenchBootstrap" in app
|
||||||
assert "from './hooks/useProjectWorkspace'" in app
|
assert "from './hooks/useProjectWorkspace'" in app
|
||||||
assert "from './hooks/useDemoWorkflow'" in app
|
assert "from './hooks/useDemoWorkflow'" in app
|
||||||
assert "from './hooks/useProviderCapabilities'" in app
|
assert "from './hooks/useProviderCapabilities'" in app
|
||||||
assert "from './hooks/useChangeDetectionWorkflow'" in app
|
assert "from './hooks/useChangeDetectionWorkflow'" in app
|
||||||
assert "from './hooks/useMapWorkspaceState'" in app
|
assert "from './hooks/useMapWorkspaceState'" in app
|
||||||
|
assert "from './hooks/useWorkbenchBootstrap'" in app
|
||||||
assert "projectsApi" not in app
|
assert "projectsApi" not in app
|
||||||
assert "areasApi" not in app
|
assert "areasApi" not in app
|
||||||
assert "datasetsApi" 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")
|
app = app_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert not app_bytes.startswith(b"\xef\xbb\xbf")
|
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 "FormEvent" not in app
|
||||||
|
assert "useEffect" not in app
|
||||||
assert "useState" 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
|
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:
|
def test_project_workspace_hook_owns_project_area_dataset_loading() -> None:
|
||||||
hook = (ROOT / "frontend" / "src" / "hooks" / "useProjectWorkspace.ts").read_text(encoding="utf-8")
|
hook = (ROOT / "frontend" / "src" / "hooks" / "useProjectWorkspace.ts").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
## Sprint 42 App entrypoint cleanup (2026-06-17)
|
||||||
|
|
||||||
Changed:
|
Changed:
|
||||||
|
|||||||
+2
-1
@@ -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] Project/area/dataset cross-load orchestration hook decomposition.
|
||||||
- [x] Demo workflow orchestration hook decomposition.
|
- [x] Demo workflow orchestration hook decomposition.
|
||||||
- [x] Final `App.tsx` import/encoding cleanup and size audit.
|
- [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
|
## Sprint 8 status
|
||||||
|
|
||||||
|
|||||||
@@ -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`.
|
- 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`.
|
- 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.
|
||||||
|
|
||||||
## Raster dependency visibility
|
## Raster dependency visibility
|
||||||
|
|
||||||
|
|||||||
+27
-31
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import './styles/app.css'
|
import './styles/app.css'
|
||||||
import { ChangeDetectionPanel } from './components/analysis/ChangeDetectionPanel'
|
import { ChangeDetectionPanel } from './components/analysis/ChangeDetectionPanel'
|
||||||
import { DatasetDetailPanel } from './components/datasets/DatasetDetailPanel'
|
import { DatasetDetailPanel } from './components/datasets/DatasetDetailPanel'
|
||||||
@@ -24,6 +24,7 @@ import { useProviderCapabilities } from './hooks/useProviderCapabilities'
|
|||||||
import { useProjectWorkspace } from './hooks/useProjectWorkspace'
|
import { useProjectWorkspace } from './hooks/useProjectWorkspace'
|
||||||
import { useQualityWorkflow } from './hooks/useQualityWorkflow'
|
import { useQualityWorkflow } from './hooks/useQualityWorkflow'
|
||||||
import { useSegmentationWorkflow } from './hooks/useSegmentationWorkflow'
|
import { useSegmentationWorkflow } from './hooks/useSegmentationWorkflow'
|
||||||
|
import { useWorkbenchBootstrap } from './hooks/useWorkbenchBootstrap'
|
||||||
|
|
||||||
function isVectorDatasetType(datasetType: string): boolean {
|
function isVectorDatasetType(datasetType: string): boolean {
|
||||||
return datasetType === 'vector' || datasetType === 'geojson'
|
return datasetType === 'vector' || datasetType === 'geojson'
|
||||||
@@ -343,36 +344,31 @@ function App(): JSX.Element {
|
|||||||
setErrorMessage,
|
setErrorMessage,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useWorkbenchBootstrap({
|
||||||
loadProjects().catch(() => null)
|
selectedProjectId,
|
||||||
loadCapabilities().catch(() => null)
|
selectedDetectionRunId,
|
||||||
loadDetectionModels().catch(() => null)
|
detectionClassFilter,
|
||||||
loadSegmentationModels().catch(() => null)
|
detectionMinConfidenceFilter,
|
||||||
}, [])
|
selectedSegmentationRunId,
|
||||||
|
segmentationClassFilter,
|
||||||
useEffect(() => {
|
segmentationMinConfidenceFilter,
|
||||||
if (!selectedProjectId) {
|
loadProjects,
|
||||||
resetProjectData()
|
loadCapabilities,
|
||||||
resetDatasetForProject()
|
loadDetectionModels,
|
||||||
resetDetectionForProject()
|
loadSegmentationModels,
|
||||||
resetSegmentationForProject()
|
loadProjectData,
|
||||||
resetExportsForProject()
|
loadDetectionRuns,
|
||||||
return
|
loadSegmentationRuns,
|
||||||
}
|
loadQualityChecks,
|
||||||
loadProjectData(selectedProjectId).catch(() => null)
|
loadExports,
|
||||||
loadDetectionRuns(selectedProjectId).catch(() => null)
|
loadDetectionResults,
|
||||||
loadSegmentationRuns(selectedProjectId).catch(() => null)
|
loadSegmentationResults,
|
||||||
loadQualityChecks(selectedProjectId).catch(() => null)
|
resetProjectData,
|
||||||
loadExports(selectedProjectId).catch(() => null)
|
resetDatasetForProject,
|
||||||
}, [selectedProjectId])
|
resetDetectionForProject,
|
||||||
|
resetSegmentationForProject,
|
||||||
useEffect(() => {
|
resetExportsForProject,
|
||||||
loadDetectionResults().catch(() => null)
|
})
|
||||||
}, [selectedDetectionRunId, detectionClassFilter, detectionMinConfidenceFilter])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadSegmentationResults().catch(() => null)
|
|
||||||
}, [selectedSegmentationRunId, segmentationClassFilter, segmentationMinConfidenceFilter])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app-shell">
|
<div className="app-shell">
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
type AsyncAction = () => Promise<unknown>
|
||||||
|
type ProjectAction = (projectId: string) => Promise<unknown>
|
||||||
|
|
||||||
|
interface WorkbenchBootstrapOptions {
|
||||||
|
selectedProjectId: string | null
|
||||||
|
selectedDetectionRunId: string
|
||||||
|
detectionClassFilter: string
|
||||||
|
detectionMinConfidenceFilter: number
|
||||||
|
selectedSegmentationRunId: string
|
||||||
|
segmentationClassFilter: string
|
||||||
|
segmentationMinConfidenceFilter: number
|
||||||
|
loadProjects: AsyncAction
|
||||||
|
loadCapabilities: AsyncAction
|
||||||
|
loadDetectionModels: AsyncAction
|
||||||
|
loadSegmentationModels: AsyncAction
|
||||||
|
loadProjectData: ProjectAction
|
||||||
|
loadDetectionRuns: ProjectAction
|
||||||
|
loadSegmentationRuns: ProjectAction
|
||||||
|
loadQualityChecks: ProjectAction
|
||||||
|
loadExports: ProjectAction
|
||||||
|
loadDetectionResults: AsyncAction
|
||||||
|
loadSegmentationResults: AsyncAction
|
||||||
|
resetProjectData: () => void
|
||||||
|
resetDatasetForProject: () => void
|
||||||
|
resetDetectionForProject: () => void
|
||||||
|
resetSegmentationForProject: () => void
|
||||||
|
resetExportsForProject: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useWorkbenchBootstrap({
|
||||||
|
selectedProjectId,
|
||||||
|
selectedDetectionRunId,
|
||||||
|
detectionClassFilter,
|
||||||
|
detectionMinConfidenceFilter,
|
||||||
|
selectedSegmentationRunId,
|
||||||
|
segmentationClassFilter,
|
||||||
|
segmentationMinConfidenceFilter,
|
||||||
|
loadProjects,
|
||||||
|
loadCapabilities,
|
||||||
|
loadDetectionModels,
|
||||||
|
loadSegmentationModels,
|
||||||
|
loadProjectData,
|
||||||
|
loadDetectionRuns,
|
||||||
|
loadSegmentationRuns,
|
||||||
|
loadQualityChecks,
|
||||||
|
loadExports,
|
||||||
|
loadDetectionResults,
|
||||||
|
loadSegmentationResults,
|
||||||
|
resetProjectData,
|
||||||
|
resetDatasetForProject,
|
||||||
|
resetDetectionForProject,
|
||||||
|
resetSegmentationForProject,
|
||||||
|
resetExportsForProject,
|
||||||
|
}: WorkbenchBootstrapOptions): void {
|
||||||
|
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])
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user