Files
brain-of-reese/.agents/phases/complete/122_image_documents/01_image_toggle.md
T
ducoterra a19d78d284
Build and Push Containers / build-and-push-app (push) Successful in 1m57s
Build and Push Containers / build-and-push-db (push) Failing after 13s
phase: 122_image_documents
**Phase 122 (image documents) — final verification pass: all green. No code changes were needed; defects found: none.**

**Verified (implementation already complete in working tree, reviewed end-to-end):**
- Toggle (`BOR_IMAGES`/`BOR_IMAGE_EXTENSIONS`/`BOR_IMAGE_DIR`, off by default) + `GET /api/config` `images` flag
- Ingest: bytes digest, `image_dir` persistent copy, `content = summary = vision description` (chat-model call; only text embedded), fail-soft skip + `images_failed` counter
- Serve/display: `/api/documents/{id}/image` route (404 matrix), viewer `<img>` + description, Sources 48px lazy thumbnails, chat inline source figure (alt = summary), agent `read` marker
- Prune guard: images-off syncs never prune `is_image` docs

**Test / lint / coverage (exact commands & outcomes):**
- `uv run pytest` → exit 0 (green; note: pytest 9.1.1 `-q` omits the final count line in output — exit code authoritative)
- `uv run pytest --cov=app --cov-report=term-missing` → **2715 passed, exit 0, TOTAL 99%** (>90% gate)
- `uv run ruff check . && uv run pyright` → "All checks passed!" / "0 errors, 0 warnings, 0 informations"
- `uv run pytest tests/e2e/test_image_documents.py -v --no-cov` → **4 passed, exit 0** (isolation)

**Completion criteria:** (1) images=true → described/embedded/displayed docs: ✅ (E2E + integration) · (2) images=false byte-identical + image docs survive sync: ✅ (E2E negative app + unit/integration) · (3) viewer + chat rendering with alt text; failed description skips + logs, sync completes: ✅ · (4) test/lint/coverage gates: ✅ · (5) commit + phase move: deferred to harness per this pass's rules (working tree left uncommitted).

**Notable deviation (pre-existing, documented in code):** image route uses `require_user` (phase-79 posture, same gate as the document content endpoint) rather than the phase text's "public" parenthetical — matches the endpoint it mirrors.

**Next pending phase:** `123_chat_image_questions`.
2026-09-25 01:54:23 -04:00

2.8 KiB

Task 01 — Image toggle: BOR_IMAGES + extensions + dir, off by default

Phase: 122_image_documents · Source: TODO.md:6 — "The user should be able to turn on and off image support in their .env depending on whether their model supports it."

Objective

The single env knob for image support exists and is surfaced — BOR_IMAGES (default false), BOR_IMAGE_EXTENSIONS, BOR_IMAGE_DIR — with GET /api/config exposing the flag for UI gating. Toggle off = byte-identical behavior to pre-phase.

Work

  1. app/config.py — three new Settings fields (house docstring style, the upload_dir/llm_retries precedents):
    • images: bool = False — BOR_IMAGES, 0/false off (LOCKED A3 default). Docstring: master switch for image-document indexing (phase 122) — off by default, enable only when the chat model supports vision (descriptions come from it).
    • image_extensions: str = "png,jpg,jpeg,webp,gif,bmp" — BOR_IMAGE_EXTENSIONS, comma-separated, case-insensitive; a property/parse into a lowercased-dotted frozenset (the import_extension_set precedent) — the image set is SEPARATE from import_extension_set (images are never user-added via BOR_IMPORT_EXTENSIONS).
    • image_dir: str = "~/bor-sources/images" — BOR_IMAGE_DIR, raw string, Path.expanduser() applied by the importer (the sources_dir/upload_dir convention) — the persistent home for image bytes (uploads are replaced, checkouts re-cloned — the copy must outlive the source file).
  2. .env.example — the three entries with the comment block: off by default + the vision-model dependency note (LOCKED A3).
  3. app/api/config.py:30 — the app_config dict gains "images": settings.images (the dict is str | bool-valued — bools already allowed). Extend the docstring: consumed by the chat composer (phase 123) to show/hide the attach control, optionally by the Sources page.
  4. ASSUMPTION: GET /api/config is already anonymous-readable (the UI gates on it pre-login in phase 123 — no auth change here).

Testing & Quality

  • Unit: tests/unit/test_image_documents.py (task 06 finalizes) — defaults (images False, extensions frozenset {".png", …} with the dotted form the matchers expect, dir default), env overrides, the frozenset parse is case-insensitive and trims spaces.
  • Integration: the existing GET /api/config test asserts the new images key (default false in the test env).
  • Coverage: >90% on the touched modules.

Completion Criteria

  • Settings() with no env: images is False, the extension set is the six defaults, image_dir is the default path.
  • GET /api/config returns images: false in the default test env (byte-check the other keys unchanged).
  • uv run pytest green; uv run ruff check . && uv run pyright clean.