New features: - In-app auto-updater with confirmation dialog, manual download fallback, restart polling, and protected file safety net - Ship layers split into 4 independent toggles (Military/Carriers, Cargo/Tankers, Civilian, Cruise/Passenger) with per-category counts - Stable entity IDs using MMSI/callsign instead of volatile array indices - Dismissible threat alert bubbles (session-scoped, survives data refresh) Performance: - GDELT title fetching is now non-blocking (background enrichment) - Removed duplicate startup fetch jobs - Docker healthcheck start_period 15s → 90s Bug fixes: - Removed fake intelligence assessment generator (OSINT-only policy) - Fixed carrier tracker GDELT 429/TypeError crash - Fixed ETag collision (full payload hash) - Added concurrent /api/refresh guard Contributors: @imqdcr (ship split + stable IDs), @csysp (dismissible alerts, PR #48) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Former-commit-id: a2c4c67da54345393f70a9b33b52e7e4fd6c049f
29 KiB
ShadowBroker Engineering Roadmap
Version: 1.0 | Created: 2026-03-12 | Codebase: v0.8.0 Purpose: Structured, agent-executable roadmap to bring ShadowBroker to production-grade quality. How to use: Each task is an atomic unit of work. An AI agent or developer can pick any task whose dependencies are met and execute it independently. Mark tasks
[x]when complete.
Architecture Overview
live-risk-dashboard/
frontend/ # Next.js 16 + React 19 + MapLibre GL
src/app/page.tsx # 621 LOC — dashboard orchestrator (19 state vars, 33 hooks)
src/components/
MaplibreViewer.tsx # 3,065 LOC — GOD COMPONENT (map + all layers + icons + popups)
CesiumViewer.tsx # 1,813 LOC — DEAD CODE (never imported)
NewsFeed.tsx # 1,088 LOC — news + entity detail panels
+ 15 more components
next.config.ts # ignoreBuildErrors: true, ignoreDuringBuilds: true (!!!)
backend/ # Python FastAPI + Node.js AIS proxy
main.py # 315 LOC — FastAPI app entry
services/
data_fetcher.py # 2,417 LOC — GOD MODULE (15+ data sources in one file)
ais_stream.py # 367 LOC — WebSocket AIS client
+ 10 more service modules
test_*.py (26 files) # ALL manual print-based, zero assertions, zero pytest
docker-compose.yml # No health checks, no resource limits
.github/workflows/docker-publish.yml # No test step, no image scanning
Scoring Baseline (Pre-Roadmap)
| Category | Score | Key Issue |
|---|---|---|
| Thread Safety | 3/10 | Race conditions on routes_fetch_in_progress, unguarded latest_data writes |
| Type Safety | 2/10 | 50+ any types, TS/ESLint errors hidden by config flags |
| Testing | 0/10 | Zero automated tests, 26 manual print scripts |
| Error Handling | 4/10 | Bare except: pass clauses, no error boundaries on panels |
| Architecture | 3/10 | Two god files (3065 + 2417 LOC), massive prop drilling |
| DevOps | 5/10 | Good Docker multi-arch, but no health checks/limits/scanning |
| Security | 4/10 | No rate limiting, no input validation, no HTTPS docs |
| Accessibility | 1/10 | No ARIA labels, no keyboard nav, no semantic HTML |
| Overall | 3.5/10 | Production-adjacent, not production-ready |
Phase 1: Stabilization & Safety
Goal: Fix things that silently corrupt data, hide bugs, or could cause production incidents. Every task here has outsized impact relative to effort.
All Phase 1 tasks are independent and can be executed in parallel.
Task 1.1: Fix thread safety bugs in data_fetcher.py
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P0 — data corruption risk |
| Dependencies | None |
File: backend/services/data_fetcher.py
Problem: routes_fetch_in_progress (~line 645) is a bare global boolean read/written from multiple threads with no lock. latest_data is written at ~lines 599, 627, 639 without _data_lock. These are TOCTOU race conditions.
Scope:
- Add a
_routes_lock = threading.Lock()and wrap all reads/writes ofroutes_fetch_in_progressanddynamic_routes_cachewith it. The current pattern (if routes_fetch_in_progress: return; routes_fetch_in_progress = True) is a classic TOCTOU race. - Find every
latest_data[...] = ...assignment NOT already under_data_lockand wrap it. Search pattern:latest_data\[. - Audit
_trails_lockusage — ensureflight_trailsdict is never accessed outside the lock. Check all references beyond the lock at ~line 1187.
Verification:
# Every latest_data write should be inside a lock
grep -n "latest_data\[" backend/services/data_fetcher.py
# Confirm routes_fetch_in_progress is no longer a bare boolean check
grep -n "routes_fetch_in_progress" backend/services/data_fetcher.py
All writes should be inside with _data_lock: or with _routes_lock: blocks.
Task 1.2: Replace bare except clauses with specific exceptions
- Complete
| Field | Value |
|---|---|
| Effort | XS (30min) |
| Priority | P0 — swallows KeyboardInterrupt, SystemExit |
| Dependencies | None |
Files:
backend/services/cctv_pipeline.py~line 223:except:→except (ValueError, TypeError) as e:+logger.debug()backend/services/liveuamap_scraper.py~lines 43, 59:except:→except Exception as e:+logger.debug()backend/services/data_fetcher.py~lines 705-706:except Exception: pass→ addlogger.warning()
Verification:
# Must return ZERO matches
grep -rn "except:" backend/ --include="*.py" | grep -v "except Exception" | grep -v "except ("
# Also check for silent swallows
grep -rn "except.*: pass" backend/ --include="*.py"
Task 1.3: Re-enable TypeScript and ESLint checking
- Complete
| Field | Value |
|---|---|
| Effort | M (3-6h) |
| Priority | P0 — currently hiding ALL type errors and lint violations |
| Dependencies | None (but pairs well with Phase 2 decomposition) |
Files:
frontend/next.config.ts— removetypescript: { ignoreBuildErrors: true }andeslint: { ignoreDuringBuilds: true }frontend/package.json— fix lint script from"lint": "eslint"to"lint": "next lint"or"lint": "eslint src/"
Scope:
- Run
npx tsc --noEmitinfrontend/and record all errors. - Fix type errors file by file. The heaviest offenders:
MaplibreViewer.tsx: ~55 occurrences of: any— create proper interfaces for props, GeoJSON features, events.page.tsx: state types need explicit interfaces.
- Replace
anywith proper interfaces. Key types needed:interface DataPayload { commercial_flights: Flight[]; military_flights: Flight[]; satellites: Satellite[]; ... } interface Flight { hex: string; lat: number; lon: number; alt_baro: number; ... } interface MaplibreViewerProps { data: DataPayload; activeLayers: ActiveLayers; ... } - Only after ALL errors are fixed, remove the two
ignore*flags fromnext.config.ts. - Fix the lint script and run
npm run lintclean.
Verification:
cd frontend && npx tsc --noEmit # Must exit 0
cd frontend && npm run lint # Must exit 0
cd frontend && npm run build # Must succeed WITHOUT ignoreBuildErrors
Task 1.4: Add transaction safety to cctv_pipeline.py
- Complete
| Field | Value |
|---|---|
| Effort | XS (30min) |
| Priority | P1 |
| Dependencies | None |
File: backend/services/cctv_pipeline.py
Scope: Wrap all SQLite write operations in try/except with explicit conn.rollback() on failure. Currently if an insert fails midway, the connection may be left dirty.
Verification: Search for all conn.execute / cursor.execute calls and confirm each write path has rollback handling.
Task 1.5: Add rate limiting and input validation to backend API
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P1 — security exposure |
| Dependencies | None |
File: backend/main.py
Scope:
- Add a simple in-memory rate limiter (e.g.,
slowapior custom middleware). Target: 60 req/min per IP for data endpoints. - Add Pydantic validation for coordinate parameters on all endpoints that accept lat/lng:
from pydantic import Field, confloat lat: confloat(ge=-90, le=90) lng: confloat(ge=-180, le=180) - Add
slowapitorequirements.txtif used.
Verification:
# Rate limit test: 100 rapid requests should get 429 after ~60
for i in $(seq 1 100); do curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8000/api/live-data/fast; done | sort | uniq -c
# Validation test: invalid coords should return 422
curl -s http://localhost:8000/api/region-dossier?lat=999&lng=999 | grep -c "error"
Task 1.6: Delete dead code
- Complete
| Field | Value |
|---|---|
| Effort | XS (30min) |
| Priority | P1 |
| Dependencies | None |
Files to delete:
frontend/src/components/CesiumViewer.tsx— 1,813 LOC, never imported anywhere- Root one-off scripts:
refactor_cesium.py,zip_repo.py,jobs.json(if tracked) - Backend one-off scripts:
check_regions.py,analyze_xlsx.py,clean_osm_cctvs.py,extract_ovens.py,geocode_datacenters.py(if tracked and not gitignored)
Also:
- Remove
fetch_bikeshare()function fromdata_fetcher.pyand its scheduler entry (if bikeshare layer no longer exists in the UI)
Verification:
grep -rn "CesiumViewer" frontend/src/ # Must return 0 matches
grep -rn "fetch_bikeshare" backend/ # Must return 0 matches
cd frontend && npm run build # Must succeed
Phase 2: Frontend Architecture — God Component Decomposition
Goal: Break MaplibreViewer.tsx (3,065 LOC) and page.tsx (621 LOC) into maintainable, testable units. This is the highest-impact refactor in the entire codebase.
Dependency chain: 2.1 + 2.2 (parallel) → 2.3 → 2.4 → 2.5
Task 2.1: Extract SVG icons and aircraft classification
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P1 |
| Dependencies | None |
Source: frontend/src/components/MaplibreViewer.tsx
New files to create:
| File | Content | Source Lines |
|---|---|---|
frontend/src/components/map/icons/AircraftIcons.ts |
All SVG path data constants (plane, heli, turboprop silhouettes) | ~1-150 |
frontend/src/components/map/icons/SvgMarkers.ts |
SVG factory functions (makeFireSvg, makeAircraftSvg, etc.) |
~60-91 |
frontend/src/utils/aircraftClassification.ts |
Military/private/commercial classifier function | ~163-169 |
Scope: Pure extraction — move constants and pure functions out. No logic changes. Update imports in MaplibreViewer.
Verification: wc -l frontend/src/components/MaplibreViewer.tsx decreases by ~200. npm run build succeeds.
Task 2.2: Extract map utilities and style definitions
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P1 |
| Dependencies | None (parallel with 2.1) |
Source: frontend/src/components/MaplibreViewer.tsx
New files to create:
| File | Content | Source Lines |
|---|---|---|
frontend/src/utils/positioning.ts |
Interpolation helpers (lerp, bearing calc) | ~171-193 |
frontend/src/components/map/styles/mapStyles.ts |
Dark/light/satellite/FLIR/NVG/CRT style URL definitions | ~195-235 |
Scope: Pure extraction of stateless helpers.
Verification: Build succeeds. Grep confirms moved functions are only defined in the new files.
Task 2.3: Extract custom hooks from MaplibreViewer
- Complete
| Field | Value |
|---|---|
| Effort | M (3-6h) |
| Priority | P1 |
| Dependencies | Tasks 2.1, 2.2 |
Source: frontend/src/components/MaplibreViewer.tsx
New files to create:
| File | Content | Source Lines |
|---|---|---|
frontend/src/hooks/useImperativeSource.ts |
The useImperativeSource hook for direct MapLibre source updates |
~268-285 |
frontend/src/hooks/useMapDataLayers.ts |
GeoJSON builder useMemo hooks (earthquakes, jamming, CCTV, data centers, fires, outages, KiwiSDR) |
~405-582 |
frontend/src/hooks/useMapImages.ts |
Image loading system for onMapLoad callback |
~585-720 |
frontend/src/hooks/useTrafficGeoJSON.ts |
Flight/ship/satellite GeoJSON construction with interpolation | ~784-900 |
Scope: Each hook accepts the map instance ref and relevant data as parameters and returns GeoJSON/state. Must handle the map.getSource() / src.setData() imperative pattern cleanly.
Verification: wc -l frontend/src/components/MaplibreViewer.tsx is under 1,500 LOC. All map layers still render correctly (manual visual check required).
Task 2.4: Extract HTML label rendering into MapMarkers component
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P2 |
| Dependencies | Task 2.3 |
Source: frontend/src/components/MaplibreViewer.tsx ~lines 1800-1910
New file: frontend/src/components/map/MapMarkers.tsx
Scope: Move the HTML overlay rendering (flight labels, carrier labels, tracked aircraft labels, cluster count badges) into a dedicated component. Receives position arrays via props.
Verification: Labels still appear on map. MaplibreViewer.tsx drops below 1,200 LOC.
Task 2.5: Introduce React Context for shared dashboard state
- Complete
| Field | Value |
|---|---|
| Effort | M (3-6h) |
| Priority | P1 |
| Dependencies | Tasks 2.1-2.4 (reduces merge conflicts) |
Source: frontend/src/app/page.tsx (621 LOC, 19 state variables, 33 hooks)
New files to create:
| File | Content |
|---|---|
frontend/src/contexts/DashboardContext.tsx |
Context provider: activeLayers, activeFilters, selectedEntity, eavesdrop state, effects, activeStyle, measureMode |
frontend/src/hooks/useDataPolling.ts |
Data fetch interval logic (fast/slow ETag polling, currently inline in page.tsx) |
frontend/src/hooks/useGeocoding.ts |
LocateBar geocoding logic (Nominatim reverse geocoding on mouse move, currently inline in page.tsx) |
Scope:
- Create
DashboardContextwrapping the 19+ state variables. - Move the
LocateBarinline component (defined inside page.tsx at ~line 26) into its own file. - Replace prop drilling to 9 child components with context consumption.
page.tsxbecomes a thin layout shell under 150 LOC.
Verification: wc -l frontend/src/app/page.tsx is under 150. All panels still receive their data. No prop names in JSX return that were previously drilled.
Phase 3: Backend Architecture — God Module Decomposition
Goal: Break data_fetcher.py (2,417 LOC) into per-source modules with proper error handling and bounded caches.
Dependency: Task 3.1 depends on Task 1.1 (thread safety fixes first). Tasks 3.2-3.4 can start after 3.1 or independently.
Task 3.1: Split data_fetcher.py into per-source fetcher modules
- Complete
| Field | Value |
|---|---|
| Effort | L (6-12h) |
| Priority | P1 |
| Dependencies | Task 1.1 (lock pattern must be correct before splitting) |
Source: backend/services/data_fetcher.py (2,417 LOC)
New directory structure:
backend/services/fetchers/
__init__.py # Re-exports for backward compat
store.py # latest_data, _data_lock, source_timestamps, get_latest_data()
scheduler.py # start_scheduler(), stop_scheduler(), APScheduler wiring
flights.py # OpenSky client, ADS-B fetch, route lookup, military classification, POTUS fleet
ships.py # AIS data processing, vessel categorization
satellites.py # TLE parsing, SGP4 propagation
news.py # RSS feeds, risk scoring, clustering
markets.py # yfinance stocks, oil prices
weather.py # RainViewer, space weather (NOAA SWPC)
infrastructure.py # CCTV, KiwiSDR, internet outages (IODA), data centers
geospatial.py # Earthquakes (USGS), FIRMS fires, GPS jamming
Scope:
- Each fetcher module exports a
fetch_*()function. store.pyholdslatest_data,_data_lock,source_timestamps, andget_latest_data().scheduler.pyimports all fetchers and wires them to APScheduler jobs.- The original
data_fetcher.pybecomes a thin re-export shim somain.pyimports remain unchanged:from .fetchers.scheduler import start_scheduler, stop_scheduler from .fetchers.store import get_latest_data, latest_data
Verification:
wc -l backend/services/data_fetcher.py # Should be under 50 (shim only)
python -c "from services.data_fetcher import start_scheduler, stop_scheduler, get_latest_data" # Must succeed
# Start backend and confirm data flows through all endpoints
Task 3.2: Add TTL and max-size bounds to all caches
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P1 |
| Dependencies | Task 3.1 (cleaner after split, but can be done before) |
Files: backend/services/data_fetcher.py (or the new fetcher modules after 3.1)
Problem caches:
_region_geocode_cache(~line 1600): unbounded dict, no TTL, grows foreverdynamic_routes_cache(~line 644): has manual pruning but should usecachetools
Scope: Replace unbounded dicts with cachetools.TTLCache:
from cachetools import TTLCache
_region_geocode_cache = TTLCache(maxsize=2000, ttl=86400) # 24h
dynamic_routes_cache = TTLCache(maxsize=5000, ttl=7200) # 2h
cachetools is already in requirements.txt.
Verification: After running for 1 hour, len(cache) stays bounded.
Task 3.3: Replace bare Exception catches with specific types and structured logging
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P2 |
| Dependencies | Task 1.2, Task 3.1 |
Files: All backend/services/*.py
Scope:
- Replace
except Exception as e: logger.error(...)with specific exceptions where possible:requests.RequestException,json.JSONDecodeError,ValueError,KeyError. - Add structured context to log messages: data source name, URL, HTTP status code.
- Ensure zero
except Exception: passpatterns remain.
Verification:
grep -rn "except Exception: pass" backend/ # Must return 0
grep -rn "except:" backend/ --include="*.py" | grep -v "except Exception" | grep -v "except (" # Must return 0
Task 3.4: Pin all Python dependencies and audit fragile ones
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P2 |
| Dependencies | None |
File: backend/requirements.txt
Scope:
- Pin all dependencies to exact versions (run
pip freezefrom working venv). - Evaluate
cloudscraper— if only used in one fetcher, document clearly or consider removal. - Evaluate
playwright— if only used byliveuamap_scraper.py, document and consider making it optional (it pulls ~150MB of browsers). - Create
backend/requirements-dev.txtfor test dependencies:pytest,httpx,pytest-asyncio.
Verification:
pip install -r requirements.txt # In fresh venv, must succeed deterministically
pip check # Must report no conflicts
Phase 4: Testing Infrastructure
Goal: Go from zero automated tests to a meaningful suite that catches regressions.
Dependency: Task 4.2 depends on Phase 2 (extracted hooks are what make frontend testing feasible). Task 4.3 depends on 4.1 and 4.2.
Task 4.1: Set up pytest for backend and write smoke tests
- Complete
| Field | Value |
|---|---|
| Effort | M (3-6h) |
| Priority | P1 |
| Dependencies | None (but benefits from Task 3.1) |
New files:
backend/tests/__init__.pybackend/tests/conftest.py— FastAPI test client fixture usinghttpx.AsyncClientbackend/tests/test_api_smoke.py— smoke tests for every endpoint inmain.pybackend/pytest.iniorpyproject.tomlpytest sectionbackend/requirements-dev.txt—pytest,httpx,pytest-asyncio
Scope:
- Create proper test infrastructure with fixtures.
- Write smoke tests: assert 200 status, valid JSON, expected top-level keys for every endpoint.
- Archive or delete the 26 manual
test_*.pyfiles (move tobackend/tests/_archived/if keeping for reference).
Verification:
cd backend && pip install -r requirements-dev.txt && pytest tests/ -v
# At least 10 tests green
Task 4.2: Set up Vitest for frontend and write component tests
- Complete
| Field | Value |
|---|---|
| Effort | M (3-6h) |
| Priority | P2 |
| Dependencies | Phase 2 (extracted hooks/utils are what make testing feasible) |
New files:
frontend/vitest.config.tsfrontend/src/__tests__/directory- Tests for: utility functions (aircraftClassification, positioning), ErrorBoundary, FilterPanel, MarketsPanel
Scope:
- Install
vitest,@testing-library/react,@testing-library/jest-dom,jsdomas devDeps. - Add
"test": "vitest run"script topackage.json. - Write tests for pure utility functions first (from Phase 2 extractions).
- Write render tests for at least 3 components.
- Do NOT test MaplibreViewer directly (needs GL context mock).
Verification:
cd frontend && npx vitest run # At least 8 tests green
Task 4.3: Add test steps to CI pipeline
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P1 |
| Dependencies | Tasks 4.1, 4.2 |
File: .github/workflows/docker-publish.yml
Scope:
- Add a
testjob that runs before build jobs. - Backend:
pip install -r requirements.txt -r requirements-dev.txt && pytest tests/ -v - Frontend:
npm ci && npm run lint && npm run build && npx vitest run - Make
build-frontendandbuild-backenddepend ontestjob.
Verification: Push a branch with a failing test → CI fails and blocks Docker build.
Phase 5: DevOps Hardening
Goal: Production-grade container config, proper .dockerignore, health checks, graceful shutdown.
All Phase 5 tasks are independent and can be executed in parallel.
Task 5.1: Add Docker health checks and resource limits
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P2 |
| Dependencies | None |
File: docker-compose.yml
Scope:
- Backend healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/live-data/fast"], interval 30s, timeout 10s, retries 3, start_period 15s. - Frontend healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"], interval 30s, timeout 10s, retries 3, start_period 20s. - Resource limits: backend 2GB memory / 2 CPUs, frontend 512MB memory / 1 CPU.
- Frontend
depends_on: backend: condition: service_healthy.
Verification:
docker compose up -d
docker ps # Shows health status column
# Kill backend process inside container, confirm Docker restarts it
Task 5.2: Create .dockerignore and fix backend Dockerfile
- Complete
| Field | Value |
|---|---|
| Effort | XS (30min) |
| Priority | P2 |
| Dependencies | None |
Files:
- New:
backend/.dockerignore— excludetest_*.py,*.json(exceptpackage*.json,news_feeds.json),*.html,*.xlsx, debug outputs - New:
.dockerignore(root) — excludenode_modules,.next,venv,.git,*.db,*.xlsx, debug JSONs - Modify:
backend/Dockerfile— changenpm installtonpm ci(~line 19)
Verification:
docker build ./backend # Image under 500MB
docker run --rm <image> ls /app/ # No debug files visible
Task 5.3: Add signal trapping for graceful shutdown in start scripts
- Complete
| Field | Value |
|---|---|
| Effort | XS (30min) |
| Priority | P2 |
| Dependencies | None |
Files:
start.sh— addtrap 'kill 0' EXIT SIGINT SIGTERMnear the topstart.bat— add error checking aftercall npm run dev
Verification: Start app → Ctrl+C → confirm no orphan node/python processes remain (ps aux | grep -E "node|python" on Unix, Task Manager on Windows).
Task 5.4: Clean root directory clutter and update .gitignore
- Complete
| Field | Value |
|---|---|
| Effort | XS (30min) |
| Priority | P3 |
| Dependencies | None |
Files: .gitignore + root directory
Scope:
- Run
git rm --cachedon any tracked files that should be ignored:TheAirTraffic Database.xlsx,zip_repo.py, etc. - Add missing patterns to
.gitignore:*.swp,*.swo,coverage/,.coverage,dist/,build/,*.tar.gz - Confirm all backend debug files (
tmp_fast.json,dump.json,debug_fast.json,merged.txt) are gitignored.
Verification:
git status # No large untracked files
git ls-files | xargs wc -c | sort -rn | head -20 # No file over 500KB tracked
Task 5.5: Document Docker secrets configuration
- Complete
| Field | Value |
|---|---|
| Effort | XS (30min) |
| Priority | P3 |
| Dependencies | None |
File: README.md
Scope: Add a section documenting the Docker Swarm secrets support already implemented in main.py (lines 8-36). The _SECRET_VARS list supports _FILE suffix convention for: AIS_API_KEY, OPENSKY_CLIENT_ID, OPENSKY_CLIENT_SECRET, LTA_ACCOUNT_KEY, CORS_ORIGINS. Include a docker-compose.yml secrets example.
Verification: The README section exists and matches the _SECRET_VARS list in main.py.
Phase 6: Long-term Quality & Accessibility
Goal: Address code quality, accessibility, and developer experience improvements that compound over time.
Dependencies: 6.1 depends on Phase 2. Others are independent.
Task 6.1: Replace inline styles with Tailwind classes
- Complete
| Field | Value |
|---|---|
| Effort | L (6-12h) |
| Priority | P3 |
| Dependencies | Phase 2 (much easier after component decomposition) |
Files: All components in frontend/src/components/
Scope:
- Audit all
style={{...}}occurrences. Heaviest offenders: MaplibreViewer.tsx, NewsFeed.tsx, FilterPanel.tsx. - Convert inline styles to Tailwind utility classes.
- For dynamic values (e.g.,
style={{ left: x + 'px' }}), keep as inline but extract repeated patterns toglobals.css:.marker-label { @apply text-xs font-mono font-bold text-white pointer-events-none; text-shadow: 0 0 3px #000; } .carrier-label { @apply text-xs font-mono font-bold text-amber-400 pointer-events-none; text-shadow: 0 0 3px #000; } - CSS variables (
var(--...)) can stay as-is for theme integration.
Verification:
grep -rn "style={{" frontend/src/components/ | wc -l # Count should decrease by 70%+
npm run build # Must succeed
Task 6.2: Add error boundaries to all child panels
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P2 |
| Dependencies | None (but cleaner after Task 2.5) |
Files:
frontend/src/components/ErrorBoundary.tsx(already exists, reuse it)frontend/src/app/page.tsx(or post-refactor layout component)
Scope: Wrap every child panel with <ErrorBoundary name="PanelName">:
- FilterPanel, NewsFeed, RadioInterceptPanel, MarketsPanel
- WorldviewLeftPanel, WorldviewRightPanel
- SettingsPanel, MapLegend
Verification: Add throw new Error("test") to MarketsPanel render → confirm error boundary catches it, other panels remain functional. Remove the throw after testing.
Task 6.3: Add basic accessibility (ARIA labels, keyboard navigation)
- Complete
| Field | Value |
|---|---|
| Effort | M (3-6h) |
| Priority | P3 |
| Dependencies | None (easier after Phase 2) |
Files: All components in frontend/src/components/
Scope:
aria-labelon all buttons, toggles, inputs.roleattributes on panel containers (role="complementary",role="navigation").aria-pressedon toggle buttons,aria-expandedon collapsible panels.- Keyboard handlers: Escape to close modals/panels, Enter to confirm.
tabIndexon custom interactive elements.- Focus management: modal open → focus modal, close → focus trigger.
Verification: Run Axe accessibility browser extension on running dashboard → zero critical violations. Tab through UI → all interactive elements reachable.
Task 6.4: Add image scanning and SBOM generation to CI
- Complete
| Field | Value |
|---|---|
| Effort | S (1-3h) |
| Priority | P3 |
| Dependencies | Task 4.3 |
File: .github/workflows/docker-publish.yml
Scope:
- Add Trivy scan step after Docker build:
uses: aquasecurity/trivy-action@masterwithseverity: CRITICAL,HIGH. - Add SBOM generation using
anchore/sbom-action, upload as build artifact. - PRs: scan but don't fail. Pushes to main: scan and fail on critical.
Verification: CI shows Trivy results in PR checks. Image with known CVE fails the build.
Dependency Graph
PHASE 1 (all parallel)
1.1 1.2 1.3 1.4 1.5 1.6
|
v
PHASE 2: 2.1 + 2.2 (parallel) ──> 2.3 ──> 2.4 ──> 2.5
|
PHASE 3: 3.1 (needs 1.1) ──> 3.2 + 3.3 (parallel)
3.4 (independent)
|
PHASE 4: 4.1 (independent) + 4.2 (needs Phase 2) ──> 4.3
|
PHASE 5 (all parallel)
5.1 5.2 5.3 5.4 5.5
|
PHASE 6: 6.1 (needs Phase 2) 6.2 6.3 6.4 (needs 4.3)
Effort Summary
| Size | Count | Hours Each | Total Hours |
|---|---|---|---|
| XS | 6 | 0.5-1h | 3-6h |
| S | 10 | 1-3h | 10-30h |
| M | 5 | 3-6h | 15-30h |
| L | 2 | 6-12h | 12-24h |
| Total | 23 tasks | ~40-90h |
Target Scores (Post-Roadmap)
| Category | Before | After | Delta |
|---|---|---|---|
| Thread Safety | 3/10 | 9/10 | +6 |
| Type Safety | 2/10 | 8/10 | +6 |
| Testing | 0/10 | 7/10 | +7 |
| Error Handling | 4/10 | 8/10 | +4 |
| Architecture | 3/10 | 8/10 | +5 |
| DevOps | 5/10 | 9/10 | +4 |
| Security | 4/10 | 7/10 | +3 |
| Accessibility | 1/10 | 6/10 | +5 |
| Overall | 3.5/10 | 8/10 | +4.5 |