[AZ-485] Add Public API barrels + STC-ARCH-01 (F4 close)

Closes architecture baseline finding F4. Every component now exposes
its Public API through `src/<component>/index.ts`; cross-component
imports go through the barrel. `scripts/check-arch-imports.mjs` plus
`STC-ARCH-01` in the static profile enforce the rule; tests in
`tests/architecture_imports.test.ts` cover AC-4/AC-5 + 2 exemption
cases. One F3-pending exemption (`classColors`) is documented in 5
places (barrel, consumer, script, doc, test) to avoid a circular
import.

Phase B cycle 1 batch 1 of 2 (epic AZ-447). Batch 2 is AZ-486
(endpoint builders) — blocked on this commit landing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 10:33:30 +03:00
parent 2071a24391
commit 23746ec61d
56 changed files with 455 additions and 101 deletions
+8 -10
View File
@@ -1,14 +1,12 @@
import { Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider } from './auth/AuthContext'
import { FlightProvider } from './components/FlightContext'
import ProtectedRoute from './auth/ProtectedRoute'
import LoginPage from './features/login/LoginPage'
import FlightsPage from './features/flights/FlightsPage'
import AnnotationsPage from './features/annotations/AnnotationsPage'
import DatasetPage from './features/dataset/DatasetPage'
import AdminPage from './features/admin/AdminPage'
import SettingsPage from './features/settings/SettingsPage'
import Header from './components/Header'
import { AuthProvider, ProtectedRoute } from './auth'
import { Header, FlightProvider } from './components'
import { LoginPage } from './features/login'
import { FlightsPage } from './features/flights'
import { AnnotationsPage } from './features/annotations'
import { DatasetPage } from './features/dataset'
import { AdminPage } from './features/admin'
import { SettingsPage } from './features/settings'
export default function App() {
return (
+2
View File
@@ -0,0 +1,2 @@
export { api, setToken, getToken, getApiBase, setNavigateToLogin } from './client'
export { createSSE } from './sse'
+1 -1
View File
@@ -3,7 +3,7 @@ import { http, HttpResponse } from 'msw'
import { act, useRef } from 'react'
import { server } from '../../tests/msw/server'
import { renderWithProviders, screen, waitFor } from '../../tests/helpers/render'
import { api, getToken, setToken } from '../api/client'
import { api, getToken, setToken } from '../api'
import { seedBearer, clearBearer } from '../../tests/helpers/auth'
// AZ-457 — Auth & token-handling at the React composition root.
+1 -1
View File
@@ -1,5 +1,5 @@
import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react'
import { api, setToken } from '../api/client'
import { api, setToken } from '../api'
import type { AuthUser } from '../types'
interface AuthState {
+2
View File
@@ -0,0 +1,2 @@
export { AuthProvider, useAuth } from './AuthContext'
export { default as ProtectedRoute } from './ProtectedRoute'
+5 -1
View File
@@ -2,7 +2,11 @@ import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { MdOutlineWbSunny, MdOutlineNightlightRound } from 'react-icons/md'
import { FaRegSnowflake } from 'react-icons/fa'
import { api } from '../api/client'
import { api } from '../api'
// classColors lives under 06_annotations until F3 moves it to its own home.
// Importing through the 06_annotations barrel would create a cycle
// (DetectionClasses -> 06_annotations barrel -> AnnotationsPage -> DetectionClasses).
// STC-ARCH-01 exempts this single path as an F3-pending edge.
import { getClassColor, FALLBACK_CLASS_NAMES } from '../features/annotations/classColors'
import type { DetectionClass } from '../types'
+1 -1
View File
@@ -1,5 +1,5 @@
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
import { api } from '../api/client'
import { api } from '../api'
import type { Flight, UserSettings } from '../types'
interface FlightState {
+1 -1
View File
@@ -1,6 +1,6 @@
import { NavLink, useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useAuth } from '../auth/AuthContext'
import { useAuth } from '../auth'
import { useFlight } from './FlightContext'
import { useState, useRef, useEffect } from 'react'
import HelpModal from './HelpModal'
+5
View File
@@ -0,0 +1,5 @@
export { default as Header } from './Header'
export { default as HelpModal } from './HelpModal'
export { default as ConfirmDialog } from './ConfirmDialog'
export { default as DetectionClasses } from './DetectionClasses'
export { FlightProvider, useFlight } from './FlightContext'
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { api } from '../../api/client'
import ConfirmDialog from '../../components/ConfirmDialog'
import { api } from '../../api'
import { ConfirmDialog } from '../../components'
import type { DetectionClass, Aircraft, User } from '../../types'
export default function AdminPage() {
+1
View File
@@ -0,0 +1 @@
export { default as AdminPage } from './AdminPage'
+3 -3
View File
@@ -1,11 +1,11 @@
import { useState, useCallback, useEffect, useRef } from 'react'
import { useResizablePanel } from '../../hooks/useResizablePanel'
import { api } from '../../api/client'
import { useResizablePanel } from '../../hooks'
import { api } from '../../api'
import MediaList from './MediaList'
import VideoPlayer, { type VideoPlayerHandle } from './VideoPlayer'
import CanvasEditor, { type CanvasEditorHandle } from './CanvasEditor'
import AnnotationsSidebar from './AnnotationsSidebar'
import DetectionClasses from '../../components/DetectionClasses'
import { DetectionClasses } from '../../components'
import { AnnotationSource, AnnotationStatus, MediaType } from '../../types'
import { getClassColor, getClassNameFallback, getPhotoModeSuffix } from './classColors'
import type { Media, AnnotationListItem, Detection } from '../../types'
@@ -1,8 +1,7 @@
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FaDownload } from 'react-icons/fa'
import { api } from '../../api/client'
import { createSSE } from '../../api/sse'
import { api, createSSE } from '../../api'
import { getClassColor } from './classColors'
import type { Media, AnnotationListItem, PaginatedResponse } from '../../types'
+3 -4
View File
@@ -1,10 +1,9 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { useDropzone } from 'react-dropzone'
import { useFlight } from '../../components/FlightContext'
import { api } from '../../api/client'
import { useDebounce } from '../../hooks/useDebounce'
import ConfirmDialog from '../../components/ConfirmDialog'
import { useFlight, ConfirmDialog } from '../../components'
import { api } from '../../api'
import { useDebounce } from '../../hooks'
import { MediaType } from '../../types'
import type { Media, PaginatedResponse, AnnotationListItem } from '../../types'
+12
View File
@@ -0,0 +1,12 @@
export { default as AnnotationsPage } from './AnnotationsPage'
// CanvasEditor remains in the Public API while F2 (cross-feature edge to
// 07_dataset) is open. Closing F2 will remove this re-export.
export { default as CanvasEditor } from './CanvasEditor'
//
// classColors symbols are NOT re-exported here. The file is logically owned
// by 11_class-colors but lives under this directory until F3 moves it. Re-
// exporting through this barrel creates a circular dependency
// AnnotationsPage -> DetectionClasses -> 06_annotations barrel -> AnnotationsPage
// because DetectionClasses (03_shared-ui) imports classColors. Consumers
// import classColors directly via `src/features/annotations/classColors`
// as a documented F3-pending exemption. STC-ARCH-01 carries the exemption.
+3 -6
View File
@@ -1,11 +1,8 @@
import { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { api } from '../../api/client'
import { useDebounce } from '../../hooks/useDebounce'
import { useResizablePanel } from '../../hooks/useResizablePanel'
import { useFlight } from '../../components/FlightContext'
import DetectionClasses from '../../components/DetectionClasses'
import ConfirmDialog from '../../components/ConfirmDialog'
import { api } from '../../api'
import { useDebounce, useResizablePanel } from '../../hooks'
import { useFlight, DetectionClasses, ConfirmDialog } from '../../components'
import CanvasEditor from '../annotations/CanvasEditor'
import type { DatasetItem, PaginatedResponse, ClassDistributionItem, AnnotationListItem, Detection, Media } from '../../types'
import { AnnotationStatus } from '../../types'
+1
View File
@@ -0,0 +1 @@
export { default as DatasetPage } from './DatasetPage'
+2 -4
View File
@@ -1,10 +1,8 @@
import { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import L from 'leaflet'
import { useFlight } from '../../components/FlightContext'
import { api } from '../../api/client'
import { createSSE } from '../../api/sse'
import ConfirmDialog from '../../components/ConfirmDialog'
import { useFlight, ConfirmDialog } from '../../components'
import { api, createSSE } from '../../api'
import FlightListSidebar from './FlightListSidebar'
import FlightParamsPanel from './FlightParamsPanel'
import FlightMap from './FlightMap'
+1
View File
@@ -0,0 +1 @@
export { default as FlightsPage } from './FlightsPage'
+1 -1
View File
@@ -1,7 +1,7 @@
import { useState, type FormEvent } from 'react'
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import { useAuth } from '../../auth/AuthContext'
import { useAuth } from '../../auth'
type UnlockStep = 'idle' | 'authenticating' | 'downloadingKey' | 'decrypting' | 'startingServices' | 'ready'
+1
View File
@@ -0,0 +1 @@
export { default as LoginPage } from './LoginPage'
+1 -1
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { api } from '../../api/client'
import { api } from '../../api'
import type { SystemSettings, DirectorySettings, Aircraft } from '../../types'
export default function SettingsPage() {
+1
View File
@@ -0,0 +1 @@
export { default as SettingsPage } from './SettingsPage'
+2
View File
@@ -0,0 +1,2 @@
export { useDebounce } from './useDebounce'
export { useResizablePanel } from './useResizablePanel'
+1
View File
@@ -0,0 +1 @@
export { default } from './i18n'
+1 -1
View File
@@ -2,7 +2,7 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import './i18n/i18n'
import './i18n'
import './index.css'
createRoot(document.getElementById('root')!).render(