Introduces two master feature flags that select which UI surface is
exposed, replacing the previous "delete legacy pages" approach with a
softer isolation:
- STUDIO_MODE_ENABLED (default false) — legacy OCR-debug surface
- RAG_PIPELINE_ENABLED (default true) — new doc-centric ingestion + viz
At least one master must be enabled (validated server-side at startup).
Sub-flags (inspect / linked / ask) are effective only when the RAG
pipeline master is on.
CHUNKS_MODE_ENABLED renamed to LINKED_MODE_ENABLED in anticipation of
T3 (Linked view replaces the Chunks tab). The DocMode union value
'chunks' is preserved for now and will be renamed in T3 alongside the
route segment, to keep this PR scoped.
Router-level guard added: requests to a route whose surface is disabled
are redirected to the other surface's landing page (or /home as a
defensive fallback). Logic extracted into a pure resolveSurface helper
with full test coverage.
i18n strings that pointed users to "Studio" rewritten to be surface-
agnostic ("from the library" / "depuis la bibliothèque") since Studio
is hidden by default in 0.6.1.
Backend:
- infra/settings.py: add studio_mode_enabled + rag_pipeline_enabled;
rename chunks_mode_enabled → linked_mode_enabled; add at-least-one
master validation in __post_init__
- api/schemas.py: HealthResponse exposes both master flags + renamed
sub-flag
- main.py: health endpoint wires the new fields
- tests: surface-flag + renamed sub-flag assertions
Frontend:
- features/feature-flags/store: add studioMode + ragPipeline registry
entries; rename chunksMode → linkedMode; sub-flags now require
ragPipeline enabled; modeFlags() maps linkedModeEnabled → key 'chunks'
(transitional)
- shared/routing/resolveSurface: pure helper + tests
- app/router: beforeEach guard consumes resolveSurface
- shared/i18n: Studio-pointing strings rewritten (en + fr) + test sync
- features/reasoning: stale "from StudioPage" comment generalized
GET /api/analyses?documentId=X retournait toutes les analyses (le query
param était ignoré). Le frontend prenait alors la première COMPLETED de
la réponse, qui pouvait être celle d'un autre doc → bboxes d'un autre
doc projetées sur l'image en cours.
Backend
- analysis_repo: nouvelle méthode find_by_document(document_id, limit, offset)
- analysis_service: expose find_by_document
- api/analyses: GET /api/analyses accepte ?documentId=... (alias Pydantic
pour respecter la règle ruff N803). Si présent, filtre via la nouvelle
méthode; sinon comportement inchangé.
- test: test_list_analyses_filtered_by_document vérifie le routing vers
find_by_document quand le query param est fourni.
Frontend (defensive)
- DocInspectTab / DocAskTab: filtre client-side
analyses.find(a => a.documentId === requestedId && a.status === 'COMPLETED')
pour rester safe même si un backend antérieur ignore le param.
Backend
- HealthResponse exposes inspectModeEnabled / chunksModeEnabled /
askModeEnabled (additive; defaults true). main.py /api/health
populates them from settings.
- infra/settings.py: three new env-var-driven booleans (defaults true)
parsed in from_env() like the existing reasoning_enabled flag.
- tests/test_api_endpoints.py: extra assertion that /api/health
surfaces the three new fields with their defaults.
Frontend — flag store
- features/feature-flags/store.ts: FeatureFlag union extended with
inspectMode / chunksMode / askMode. New entries in featureRegistry
are gated on context fields populated from health. Missing fields
fall back to true so a frontend pointed at an older backend keeps
every mode visible.
- store gains a modeFlags() helper returning Record<DocMode, boolean>
so the routing guard does not need to know the FeatureFlag union.
Frontend — routing
- shared/routing/resolveMode.ts: pure resolver. If the requested mode
is enabled, return it; else first enabled in priority ask > chunks
> inspect; else null.
- app/router/index.ts: beforeEach guard scoped to ROUTES.DOC_WORKSPACE.
Disabled mode → rewrite ?mode= to the first enabled one. All three
off → redirect to /docs?reason=no-mode-enabled.
Frontend — flash
- pages/DocsLibraryPage.vue: shows a banner when ?reason=no-mode-enabled
is set. #211 will move this into the proper library page banner.
- i18n flags.allModesDisabled added in fr + en.
Tests
- shared/routing/resolveMode.test.ts (6 cases): every (requested,
enabled) combination including all-disabled, priority order,
missing requested.
- features/feature-flags/store.test.ts: three new cases covering the
new fields in /api/health, fall-back-to-true on missing fields, and
modeFlags() shape.
Refs #210
- Conditionally mount ingestion router only when OpenSearch + embedding are configured
- Add `ingestionAvailable` field to /api/health response
- Add `ingestion` feature flag to frontend (hides Search nav, Ingest button,
OpenSearch badge, indexed badges/filters when disabled)
- Skip ingestion polling when flag is off
- Make OpenSearch + embedding optional in docker-compose via profiles
- Add docker-compose.ingestion.yml override for full-stack ingestion
- Set BATCH_PAGE_SIZE=5 default in Docker local image
- Lead Quick Start with one-liner docker run command
- Document ingestion as opt-in with dedicated section
- Add BATCH_PAGE_SIZE, MAX_FILE_SIZE_MB, MAX_PAGE_COUNT, RATE_LIMIT_RPM to config tables
- Update test counts (380 backend, 159 frontend)
- Date CHANGELOG 0.4.0, bump frontend version to 0.4.0
- Sync CONTRIBUTING.md with E2E Karate test sections
Closes#180
- Add DocumentRepository and AnalysisRepository protocols in domain/ports.py (#128)
- Refactor persistence repos from module functions to SqliteDocumentRepository
and SqliteAnalysisRepository classes
- Inject repos into AnalysisService and new DocumentService class via
constructor, removing direct imports of persistence and infra.settings (#129)
- Move _merge_results, _classify_error, _extract_html_body to domain/services.py (#130)
- Update main.py composition root to build and wire all dependencies
- Switch api/documents.py to Depends pattern matching api/analyses.py
- Update all tests to use injected mocks instead of module-level patches
Closes#128, closes#129, closes#130
Replace hardcoded 5 MB upload limit with a configurable setting.
Backend exposes the value via /api/health, frontend reads it
dynamically for validation and UI messages.
Closes#48
Prevents PyTorch/Docling pipeline crashes on HF Spaces CPU by:
- Reducing max file size from 50 MB to 5 MB
- Adding configurable MAX_PAGE_COUNT setting (env var, default unlimited)
- Increasing conversion timeout from 600s to 900s
- Adding frontend upload validation with explicit error messages
- Exposing maxPageCount via /api/health for dynamic UI hints
Adapt test expectations to external changes: upload returns 200,
ValueError yields 400, and schemas now accept both snake_case and
camelCase via AliasChoices.
All endpoint functions, lifespan context manager, and get_connection now
have explicit return types. Upload endpoint returns 201 Created instead
of 200 to follow REST conventions.
Move /health to /api/health on backend and update the frontend
feature flag store to match. Without this, the combined Docker
image nginx proxy could not reach the endpoint and feature flags
(chunking/prepare mode) failed to load.
- Delete domain/parsing.py (broke hexagonal layering by importing infra)
- Migrate all tests to import directly from domain.value_objects and
infra.local_converter
- Rewrite ServeConverter to match real Docling Serve v1 API contract:
options sent as individual form fields (not JSON blob), response
parsed from document.json_content (DoclingDocument), proper bbox
coord_origin handling (TOPLEFT/BOTTOMLEFT)
- Transmit all conversion options including generate_picture_images
- Replace fragile lazy import circular dep with FastAPI Depends() +
app.state for AnalysisService injection
- Add frontend file size validation (50MB) before upload
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract domain value objects and ports from parsing.py, move Docling-specific
code to infra/local_converter.py, and convert analysis_service to a class
with injected DocumentConverter. This prepares the codebase for plugging in
alternative conversion backends (e.g. Docling Serve) via the Protocol pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>