All JSON config files (AI settings, doc settings, appearance, themes) now live
in the 'config' bucket of storage-service instead of a shared Docker volume.
- backend/core/config_storage.py: new async HTTP helpers for config bucket r/w
- backend/core/app_config.py: fully async rewrite; all load_*/save_*/seed_*
functions use config_storage instead of filesystem
- backend/routers/settings.py: all asyncio.to_thread() wrappers removed; direct
await calls throughout; update_theme reads via load_theme_by_id()
- backend/main.py: await seed_builtin_themes() directly (no to_thread)
- ai-service: remove CONFIG_PATH, add STORAGE_SERVICE_URL; config_reader now
fetches from storage-service via httpx
- doc-service: config_reader rewritten to fetch/write via storage-service
- docker-compose: remove app_config volume; add storage-service depends_on for
ai-service; remove DATA_DIR and CONFIG_PATH from doc-service
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Folder names like "invoices" and "vendor-invoices" are now converted to
"Invoices" and "Vendor-Invoices" when the watcher auto-creates categories,
matching the naming convention enforced on user-created categories.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix: list_plugins imported _REGISTRY as a direct reference to the
empty list that existed at import time; register_services() replaces
_REGISTRY with a new list so the imported reference was always [].
Added get_registry() helper so callers access the live list via the
module namespace. GET /api/plugins now correctly returns accessible
plugins for the current user.
- Fix: switch watchdog from InotifyObserver to PollingObserver. Inotify
events from the macOS host are not forwarded through the Docker bind
mount, so new files were only detected via the startup scan. PollingObserver
(1s default interval) works reliably on all platforms including
macOS+Docker bind mounts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces a manifest contract so feature containers self-describe their
settings (JSON Schema + access rules). Backend and frontend gain generic
plugin proxy and dynamic Extensions UI with zero feature-specific code.
Doc-service is the first plugin consumer: exposes /plugin/manifest and
/plugin/settings, adds a watchdog-based file watcher that auto-ingests
PDFs from a mounted directory, maps subfolders to categories, supports
AI-suggested folder/filename (user-confirmed), and enforces a no-remove
policy. Access is gated by is_superuser or doc-service-admin group.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each feature service owns its system prompt in its config JSON on the
shared volume. The AI Settings page now has General and System Prompts
tabs — admins can view and edit any service's prompts at runtime with
changes taking effect within 30 s (config cache TTL).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AI now returns a short descriptive title per document (e.g. "ACME Corp
Invoice April 2026"). Title is stored in a new documents.title column
(migration 0002), shown in the row header instead of the raw filename,
and editable inline via PATCH /documents/{id}/title. Filename is shown
as a subtitle when a title exists.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All feature containers now POST messages to ai-service (port 8010) instead
of calling AI providers directly. ai-service routes to LM Studio, Ollama,
or Anthropic based on /config/ai_service_config.json. doc-service AI
providers removed; replaced by httpx ai_client.py. Backend settings
restructured to /api/settings/ai. Frontend gets dedicated AIAdminSettingsPage
and AI Service card in AppsPage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
config_reader.py now merges environment variables (AI_PROVIDER,
LMSTUDIO_BASE_URL, LMSTUDIO_API_KEY, LMSTUDIO_MODEL, OLLAMA_*,
ANTHROPIC_*) on top of the JSON config file, so the dev .env file
can pin the AI connection without writing to the shared config volume.
docker-compose.dev.yml loads features/doc-service/.env (gitignored)
into the doc-service container so the token is never committed.
.env.example updated with all supported override variables and comments.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pytest suite for doc-service: 20+ tests covering category CRUD,
document upload/get/delete/patch, ownership isolation, category
assignment, AI processing (mock), and live PDF tests (auto-skipped
when tests/pdfs/ is empty)
- Minimal in-memory PDF builder in conftest so tests run without any
fixture files; real PDFs can be dropped into tests/pdfs/ to activate
live extraction tests
- AI prompt updated to return suggested_categories (2–5 short names)
- Frontend: SuggestionChip component in DocumentRow shows AI-suggested
categories after processing; "Assign" links to an existing category,
"Create & Assign" creates it first, ✕ dismisses locally
- Default AI provider changed to LM Studio at
http://host.docker.internal:1234/v1 (host.docker.internal resolves
to the macOS host from inside Docker Desktop)
- tests/pdfs/ directory tracked via .gitkeep; *.pdf excluded by .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New `features/doc-service` FastAPI microservice: PDF upload, async
text extraction (pdfplumber), AI classification via Anthropic/Ollama/
LM Studio, per-user categories, file download
- Alembic migration isolated with `alembic_version_doc_service` table
- Main backend: httpx proxy routers for /api/documents/* and
/api/documents/categories/*, admin settings API at /api/settings/*
- Runtime config in /config/doc_service_config.json (shared Docker
volume); api_key masking on reads; atomic write with os.replace()
- Frontend: DocumentsPage, DocumentAdminSettingsPage, updated AppsPage
launcher hub, simplified Nav (removed Settings link), new routes
- docker-compose: doc-service service, doc_data + app_config volumes,
removed internal:true from backend-net for outbound AI API calls
- Fix pre-commit hook: probe Docker socket path so git subprocess picks
up Docker Desktop on macOS
- Fix security_check.py: use sys.executable for bandit so venv python
is used instead of system python
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>