Splits each page into its own lazily-imported chunk via React.lazy
with Suspense fallback (a skeleton matching the dashboard layout
shape). Adds a vite manualChunks function that pushes heavy third-
party libraries into long-lived vendor chunks so page chunks stay
small and the vendor cache survives release cycles.
Vendor groupings: vendor-react, vendor-router, vendor-tanstack,
vendor-radix (+ cmdk), vendor-motion, vendor-recharts (+ d3 deps),
vendor-axios, vendor-state (zustand), vendor-toast (sonner),
vendor-lucide, vendor (everything else).
Build output (before -> after, gzipped):
initial entry 348.65 kB -> 8.75 kB
largest chunk 1163.97 kB -> 81.65 kB (vendor-recharts, only
loaded on Dashboard + SystemHealth)
build warning "chunks > 500 kB" -> gone
DocumentsPage, SettingsPage, etc. no longer pull recharts into their
critical path; the dashboard pays the chart cost once, cached.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds defence-in-depth shared-secret auth that activates when API_KEY
is set. Behaviour:
- empty API_KEY (dev default): every request allowed, middleware is
not even installed;
- non-empty API_KEY: every request under APP_API_PREFIX except
/health must carry X-API-Key: <value> or
Authorization: Bearer <value>. /, /docs, /redoc, /openapi.json and
CORS preflight stay open. hmac.compare_digest is used for the
constant-time comparison.
The middleware resolves settings lazily so test fixtures can reload
app.config and have the new API_KEY take effect on the next install.
Tests (tests/test_api_security.py, 5 cases):
- /health remains open;
- protected route rejects missing key (401);
- protected route accepts X-API-Key header;
- protected route accepts Authorization: Bearer header;
- protected route rejects a wrong key.
Frontend:
- VITE_API_KEY env reads the key and Axios injects it on every
request, falling back to no header when empty so SSO/reverse-proxy
deployments stay unchanged.
- vite-env.d.ts adds the new env entry.
Docs/ops:
- .env.example documents the dev-default empty key;
- .env.prod.example marks API_KEY as a required rotation point;
- docker-compose.yml forwards API_KEY (defaults to empty);
- docker-compose.prod.yml fails the stack with ?:required when API_KEY
is missing;
- RUNBOOK gains an API authentication section with header examples
and the reverse-proxy + key layering recommendation.
pytest -q: 33 passed (5 new security + 28 prior).
npx tsc --noEmit: clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app/indexing/qdrant_client.py: remove the identity-only _qid()
helper and pass chunk_id straight to PointStruct (Qdrant accepts
the UUID string directly).
- services/types.ts: SearchHit gets an explicit, optional
ocr_confidence field so consumers can type the value instead of
casting through metadata.
- widgets/SearchResultCard.tsx: replaces the
(hit.metadata as { ocr_confidence? }) cast with the new field. No
behavior change when the backend omits it.
tsc --noEmit: clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- vite-env.d.ts now declares ImportMetaEnv with the three VITE_*
variables the project uses, restoring proper typing for
import.meta.env in apiClient.ts.
- QualityFlag.tsx widens its 'flags' prop to accept the domain
QualityFlags type, the loose Record form used in mocks, or null,
ending the structural-mismatch errors at five callsites
(DocumentsPage, DocumentViewerPage, QualityControlPage,
ChunkPreview, SearchResultCard).
- DashboardPage trend callbacks are typed against DashboardStats so
the implicit-any complaints disappear without weakening intent.
npx tsc --noEmit -> clean. vite build -> ok.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two-job CI (backend + frontend) running ruff, pytest (unit only -
skipping heavy ML deps), docker compose config validation for both dev
and prod overlays, plus npm ci -> eslint -> tsc -> vite build for the
frontend.
ESLint config uses the v9 flat-config format that the project was
already on (eslint v9 dropped .eslintrc support); replaces the broken
'eslint . --ext' invocation and adds @typescript-eslint, react-hooks,
and react-refresh plugins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>