diff --git a/.agents/phases/todo/104_chip_sizing_question_cap/00_phase.md b/.agents/phases/complete/104_chip_sizing_question_cap/00_phase.md similarity index 100% rename from .agents/phases/todo/104_chip_sizing_question_cap/00_phase.md rename to .agents/phases/complete/104_chip_sizing_question_cap/00_phase.md diff --git a/.agents/phases/todo/104_chip_sizing_question_cap/01_chip_ellipsis_css.md b/.agents/phases/complete/104_chip_sizing_question_cap/01_chip_ellipsis_css.md similarity index 100% rename from .agents/phases/todo/104_chip_sizing_question_cap/01_chip_ellipsis_css.md rename to .agents/phases/complete/104_chip_sizing_question_cap/01_chip_ellipsis_css.md diff --git a/.agents/phases/todo/104_chip_sizing_question_cap/02_chip_tooltip_aria.md b/.agents/phases/complete/104_chip_sizing_question_cap/02_chip_tooltip_aria.md similarity index 100% rename from .agents/phases/todo/104_chip_sizing_question_cap/02_chip_tooltip_aria.md rename to .agents/phases/complete/104_chip_sizing_question_cap/02_chip_tooltip_aria.md diff --git a/.agents/phases/todo/104_chip_sizing_question_cap/03_composer_question_cap.md b/.agents/phases/complete/104_chip_sizing_question_cap/03_composer_question_cap.md similarity index 100% rename from .agents/phases/todo/104_chip_sizing_question_cap/03_composer_question_cap.md rename to .agents/phases/complete/104_chip_sizing_question_cap/03_composer_question_cap.md diff --git a/.agents/phases/todo/104_chip_sizing_question_cap/04_e2e_suite_commit.md b/.agents/phases/complete/104_chip_sizing_question_cap/04_e2e_suite_commit.md similarity index 100% rename from .agents/phases/todo/104_chip_sizing_question_cap/04_e2e_suite_commit.md rename to .agents/phases/complete/104_chip_sizing_question_cap/04_e2e_suite_commit.md diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.err b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.md b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.md new file mode 100644 index 0000000..fd193f6 --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.md @@ -0,0 +1,11 @@ +All completion criteria verified. Final report: + +**Phase 104 — final verification pass: all green** +- Verified (no code changes needed): single-line ellipsized `.suggestion-chip` CSS + deleted `.maybe-try` override, `renderChips` full-text `title` + `aria-label`-when-clipped, `maxlength="4000"` + `#char-count` counter + `handleSend` over-cap guard, unit pins file, schemas boundary pins, dedicated E2E suite. +- E2E (isolation): `uv run pytest tests/e2e/test_chip_sizing_question_cap.py -v --no-cov` → **6 passed**; regressions: `test_suggestion_chips.py` 8 passed, `test_pinned_composer.py` 4 passed, `test_responsive_polish.py` 7 passed, `test_chat_history.py` 5 passed. +- `uv run pytest` → **2102 passed**; `--cov=app` → **99%** (>90%); `uv run ruff check . && uv run pyright` → clean, 0 errors. +- Criteria: chip E2E (single-line, clipped, title+aria-label full text) ✅; paste caps at exactly 4,000, send streams, counter hides ✅; programmatic 5,000-char fill → banner, no turn, text kept ✅; 4,000/4,001 boundary pinned + HTML maxlength == JS constant cross-file pin ✅. +- Diff scope: `frontend/`, new unit file, `tests/unit/test_schemas.py`, new E2E file, phase files — **no `app/` diff, no migration, no `shared.js` diff**. +- Deviations: 4 regression test files touched — 2 genuine DOM-pin conflicts from the new `#char-count` child (explicitly anticipated by the overview) + 3 documented **pre-existing E2E flake fixes** (smooth-scroll race, tab-walk heuristic, 10 ms timeout), each verified pre-existing on the pre-phase-104 tree. +- No commit made (harness commits per the execution protocol override). +- Next pending phase: `98_sync_summary_visibility`. diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.validate b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.validate new file mode 100644 index 0000000..15c88de --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__00_phase.a1.validate @@ -0,0 +1,97 @@ +........................................................................ [ 3%] +........................................................................ [ 6%] +........................................................................ [ 10%] +........................................................................ [ 13%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 27%] +........................................................................ [ 30%] +........................................................................ [ 34%] +........................................................................ [ 37%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 47%] +........................................................................ [ 51%] +........................................................................ [ 54%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 65%] +........................................................................ [ 68%] +........................................................................ [ 71%] +........................................................................ [ 75%] +........................................................................ [ 78%] +........................................................................ [ 82%] +........................................................................ [ 85%] +........................................................................ [ 89%] +........................................................................ [ 92%] +........................................................................ [ 95%] +........................................................................ [ 99%] +.............. [100%] +=============================== warnings summary =============================== +.venv/lib/python3.13/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.13.13-final-0 _______________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 181 0 100% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 94 0 100% +app/api/docs.py 134 1 99% +app/api/git_sources.py 229 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 115 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 140 0 100% +app/core/__init__.py 0 0 100% +app/core/auth.py 45 0 100% +app/core/caching.py 124 0 100% +app/core/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 21 0 100% +app/main.py 66 0 100% +app/models.py 125 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 316 1 99% +app/rag/archive_upload.py 128 0 100% +app/rag/chunker.py 206 4 98% +app/rag/folder_summaries.py 121 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 197 3 98% +app/rag/llm.py 243 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 150 3 98% +app/rag/scaffolding.py 55 0 100% +app/rag/source_removal.py 41 0 100% +app/rag/sources_meta.py 16 0 100% +app/rag/suggestions.py 27 0 100% +app/rag/summarizer.py 24 0 100% +app/schemas.py 307 0 100% +-------------------------------------------------- +TOTAL 3866 15 99% +coverage gate: app/ 99% (>90%) OK +All checks passed! +0 errors, 0 warnings, 0 informations +WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414). +Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest` + +validation OK diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.err b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.md b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.md new file mode 100644 index 0000000..bdba8c7 --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.md @@ -0,0 +1,18 @@ +Task 01 complete — all gates green. + +**Implemented** +- `frontend/assets/styles.css`: base `.suggestion-chip` rule gains the single-line contract (`white-space: nowrap`, `overflow: hidden`, `text-overflow: ellipsis`, `max-width: 100%`, `min-width: 0`) with the phase-104 provenance comment (incl. the folded-in phase-07 origin). +- Deleted the fully subsumed `.maybe-try .suggestion-chip` override (its phase-07 provenance lives in the base rule's comment, phrased without the literal selector so the `count == 0` pin holds). +- Created `tests/unit/test_chip_sizing_question_cap.py` — the phase's unit-pins file with the three task-01 CSS pins (ellipsis quartet + pill contract kept, override gone / `.maybe-try` group stays, ≤640px `flex: 0 0 auto` track untouched). +- Mobile rule and `.shared-shell` static-chip rule untouched (A6); no JS/HTML/E2E changes (later tasks). + +**Test / lint / coverage** +- `uv run pytest tests/unit/ -q` → 1564 passed +- `uv run pytest` (unit+integration, DB up) → 2089 passed +- `uv run pytest --cov=app --cov-report=term-missing` → TOTAL 99% (>90% gate) +- `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings +- `git diff --stat` → `frontend/assets/styles.css` only (+ new unit file; no `app/` diff) + +**Decisions:** none deviating — followed the phase design verbatim. + +**Next pending task:** `.agents/phases/todo/104_chip_sizing_question_cap/02_chip_tooltip_aria.md` diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.validate b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.validate new file mode 100644 index 0000000..9ca2a44 --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__01_chip_ellipsis_css.a1.validate @@ -0,0 +1,97 @@ +........................................................................ [ 3%] +........................................................................ [ 6%] +........................................................................ [ 10%] +........................................................................ [ 13%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 24%] +........................................................................ [ 27%] +........................................................................ [ 31%] +........................................................................ [ 34%] +........................................................................ [ 37%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 48%] +........................................................................ [ 51%] +........................................................................ [ 55%] +........................................................................ [ 58%] +........................................................................ [ 62%] +........................................................................ [ 65%] +........................................................................ [ 68%] +........................................................................ [ 72%] +........................................................................ [ 75%] +........................................................................ [ 79%] +........................................................................ [ 82%] +........................................................................ [ 86%] +........................................................................ [ 89%] +........................................................................ [ 93%] +........................................................................ [ 96%] +........................................................................ [ 99%] +. [100%] +=============================== warnings summary =============================== +.venv/lib/python3.13/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.13.13-final-0 _______________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 181 0 100% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 94 0 100% +app/api/docs.py 134 1 99% +app/api/git_sources.py 229 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 115 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 140 0 100% +app/core/__init__.py 0 0 100% +app/core/auth.py 45 0 100% +app/core/caching.py 124 0 100% +app/core/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 21 0 100% +app/main.py 66 0 100% +app/models.py 125 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 316 1 99% +app/rag/archive_upload.py 128 0 100% +app/rag/chunker.py 206 4 98% +app/rag/folder_summaries.py 121 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 197 3 98% +app/rag/llm.py 243 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 150 3 98% +app/rag/scaffolding.py 55 0 100% +app/rag/source_removal.py 41 0 100% +app/rag/sources_meta.py 16 0 100% +app/rag/suggestions.py 27 0 100% +app/rag/summarizer.py 24 0 100% +app/schemas.py 307 0 100% +-------------------------------------------------- +TOTAL 3866 15 99% +coverage gate: app/ 99% (>90%) OK +All checks passed! +0 errors, 0 warnings, 0 informations +WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414). +Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest` + +validation OK diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.err b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.md b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.md new file mode 100644 index 0000000..a538074 --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.md @@ -0,0 +1,15 @@ +Task 02 complete. All checks green. + +**Implemented** +- `frontend/assets/app.js` `renderChips`: `btn.title = text` on every chip (full-text hover tooltip, owner A2, source-chip precedent) + `aria-label` = full text only when `btn.scrollWidth > btn.clientWidth` (clipped case), with the phase-104 provenance comment; nothing else in `renderChips` touched +- `tests/unit/test_chip_sizing_question_cap.py`: added `APP_JS`/`_app_js()`/`_render_chips_body()` (brace-matched extraction, handles the `{ onSelect }` destructuring) + two task-02 pins (`btn.title = text`; the guard-anchored `setAttribute("aria-label", text)`) + module docstring updated + +**Results** +- `uv run pytest tests/unit/` → 1566 passed +- `uv run pytest` (unit + integration) → 2091 passed +- `uv run pytest --cov=app --cov-report=term` → 2091 passed, `app/` coverage 99% (>90% gate) +- `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors + +**Decisions:** `shared.js` untouched (its "no renderChips" pin stays green — it guards a different file). + +**Next pending task:** `.agents/phases/todo/104_chip_sizing_question_cap/03_composer_question_cap.md` diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.validate b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.validate new file mode 100644 index 0000000..98f85ea --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__02_chip_tooltip_aria.a1.validate @@ -0,0 +1,97 @@ +........................................................................ [ 3%] +........................................................................ [ 6%] +........................................................................ [ 10%] +........................................................................ [ 13%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 24%] +........................................................................ [ 27%] +........................................................................ [ 30%] +........................................................................ [ 34%] +........................................................................ [ 37%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 48%] +........................................................................ [ 51%] +........................................................................ [ 55%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 65%] +........................................................................ [ 68%] +........................................................................ [ 72%] +........................................................................ [ 75%] +........................................................................ [ 79%] +........................................................................ [ 82%] +........................................................................ [ 86%] +........................................................................ [ 89%] +........................................................................ [ 92%] +........................................................................ [ 96%] +........................................................................ [ 99%] +... [100%] +=============================== warnings summary =============================== +.venv/lib/python3.13/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.13.13-final-0 _______________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 181 0 100% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 94 0 100% +app/api/docs.py 134 1 99% +app/api/git_sources.py 229 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 115 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 140 0 100% +app/core/__init__.py 0 0 100% +app/core/auth.py 45 0 100% +app/core/caching.py 124 0 100% +app/core/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 21 0 100% +app/main.py 66 0 100% +app/models.py 125 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 316 1 99% +app/rag/archive_upload.py 128 0 100% +app/rag/chunker.py 206 4 98% +app/rag/folder_summaries.py 121 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 197 3 98% +app/rag/llm.py 243 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 150 3 98% +app/rag/scaffolding.py 55 0 100% +app/rag/source_removal.py 41 0 100% +app/rag/sources_meta.py 16 0 100% +app/rag/suggestions.py 27 0 100% +app/rag/summarizer.py 24 0 100% +app/schemas.py 307 0 100% +-------------------------------------------------- +TOTAL 3866 15 99% +coverage gate: app/ 99% (>90%) OK +All checks passed! +0 errors, 0 warnings, 0 informations +WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414). +Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest` + +validation OK diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.err b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.md b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.md new file mode 100644 index 0000000..a2a2ccd --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.md @@ -0,0 +1,14 @@ +All gates green. Final report: + +**Task 03 complete — visible 4,000-char question cap (maxlength + counter + guard + server-boundary pin)** + +- `#message-input` gains `maxlength="4000"` with provenance comment; `#char-count` counter added inside `.chat-bottom` between the actions row and composer, hidden by default, with the phase-104 decision comment (not a live region; B3 text+color) +- `app.js`: `MAX_QUESTION_CHARS=4000` / `CHAR_COUNT_SHOW_AT=3200` (80%), `updateCharCount()` (RAW length, `.is-max` + "— character limit" copy at/over cap, honest over-cap reading), called at all four `input.value` mutation sites; `handleSend` over-cap guard (banner, no turn, text kept) before the clear +- `styles.css`: `.char-count` / `.char-count.is-max` rules — AA-verified on `--bg` (8.6:1, 10.4:1) with ratios recorded in the comment +- Unit pins: `test_chip_sizing_question_cap.py` extended (HTML/JS/CSS pins + single-source cross-file pin HTML==JS==schema); `test_schemas.py` 4,000/4,001 `ChatRequest.message` boundary; two conflicting unit pins (`test_pinned_composer.py`, `test_save_chat_ui.py`) updated to carry the new counter child (genuine conflicts anticipated by the phase) +- **Defect fixes in prior work:** `e2e/test_pinned_composer.py::test_stop_is_reachable_from_scrolled_up` was racy (proven on the pristine tree — the test scrolled up mid smooth-`scrollReveal`; reads caught y=23–119) → deterministic arrival-wait on the captured scroll target; `e2e/test_suggestion_chips.py` `_api_suggestions` `timeout=10` (10 ms) sat below the ~12 ms steady-state localhost round trip → 10 s house pattern + +- Results: `uv run pytest tests/unit/ -q` → 1577 passed; `uv run pytest --cov=app` → 2102 passed, **99%** coverage (>90%); `uv run ruff check .` → clean; `uv run pyright` → 0 errors +- Regression E2Es (isolated): `test_pinned_composer.py` 4 passed (×6 consecutive), `test_suggestion_chips.py` 8 passed (×2), `test_responsive_polish.py` 7 passed, `test_chat_history.py` 5 passed; no `app/` diff + +- Next pending task: `04_e2e_suite_commit.md` (dedicated E2E suite + full gate + atomic commit) diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.validate b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.validate new file mode 100644 index 0000000..15c88de --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__03_composer_question_cap.a1.validate @@ -0,0 +1,97 @@ +........................................................................ [ 3%] +........................................................................ [ 6%] +........................................................................ [ 10%] +........................................................................ [ 13%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 27%] +........................................................................ [ 30%] +........................................................................ [ 34%] +........................................................................ [ 37%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 47%] +........................................................................ [ 51%] +........................................................................ [ 54%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 65%] +........................................................................ [ 68%] +........................................................................ [ 71%] +........................................................................ [ 75%] +........................................................................ [ 78%] +........................................................................ [ 82%] +........................................................................ [ 85%] +........................................................................ [ 89%] +........................................................................ [ 92%] +........................................................................ [ 95%] +........................................................................ [ 99%] +.............. [100%] +=============================== warnings summary =============================== +.venv/lib/python3.13/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.13.13-final-0 _______________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 181 0 100% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 94 0 100% +app/api/docs.py 134 1 99% +app/api/git_sources.py 229 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 115 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 140 0 100% +app/core/__init__.py 0 0 100% +app/core/auth.py 45 0 100% +app/core/caching.py 124 0 100% +app/core/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 21 0 100% +app/main.py 66 0 100% +app/models.py 125 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 316 1 99% +app/rag/archive_upload.py 128 0 100% +app/rag/chunker.py 206 4 98% +app/rag/folder_summaries.py 121 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 197 3 98% +app/rag/llm.py 243 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 150 3 98% +app/rag/scaffolding.py 55 0 100% +app/rag/source_removal.py 41 0 100% +app/rag/sources_meta.py 16 0 100% +app/rag/suggestions.py 27 0 100% +app/rag/summarizer.py 24 0 100% +app/schemas.py 307 0 100% +-------------------------------------------------- +TOTAL 3866 15 99% +coverage gate: app/ 99% (>90%) OK +All checks passed! +0 errors, 0 warnings, 0 informations +WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414). +Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest` + +validation OK diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.err b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.err new file mode 100644 index 0000000..e69de29 diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.md b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.md new file mode 100644 index 0000000..c54e8d2 --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.md @@ -0,0 +1,11 @@ +All gates green. Final report: + +**Phase 104 · Task 04 — E2E suite + regression gates** + +- New dedicated suite `tests/e2e/test_chip_sizing_question_cap.py` (6 tests: single-line ellipsized long chip with `title`/`aria-label` full text, seed-chip tooltip-without-aria contrast, counter threshold 100/3500, paste-path hard cap at 4,000 + clean submit at cap, over-cap programmatic-fill guard (banner, no turn, text kept), short-flow never-visible counter with MutationObserver tripwire) — drafted in the interrupted prior attempt, verified green +- Fixed a **pre-existing flake** in `test_responsive_polish.py::test_a11y_landmarks_and_labels` (verified identical on the pristine pre-phase-104 tree): `_tab_outline_walk` keyed elements by 24-char text prefix, so two identical opener chips left in shared-DB `saved_chats` by `test_pinned_composer.py` cut the walk short (2 < 3); now uses true element identity via a stamped `data-tabwalk-id`. No behavior change to the asserted contract +- E2E in isolation: `uv run pytest tests/e2e/test_chip_sizing_question_cap.py -v --no-cov` → 6 passed; regressions each green in isolation: `test_suggestion_chips` 8, `test_pinned_composer` 4, `test_responsive_polish` 7 (incl. adversarial post-pinned_composer state, 3 runs), `test_chat_history` 5 +- Full gate: `uv run pytest` → 2102 passed; `uv run pytest --cov=app --cov-report=term-missing` → 2102 passed, `app/` **99%** (>90%); `uv run ruff check . && uv run pyright` → clean, 0 errors +- `git diff --stat` scoped: `frontend/` (app.js/styles.css/index.html), new unit+E2E files, `test_schemas.py`, pin updates (`test_pinned_composer` unit, `test_save_chat_ui`), prior-task e2e flake fixes (`test_pinned_composer`, `test_suggestion_chips`) + this fix; NO `app/` code diff, no migration, no `shared.js`, no `pyproject.toml`/`uv.lock` +- Phase dir move + atomic `--no-gpg-sign` commit: left to the harness per executor rules (overrides task step 5) +- Next pending task: none in this phase (04 is the last); next phase = whatever follows `104` in `.agents/phases/todo/` diff --git a/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.validate b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.validate new file mode 100644 index 0000000..15c88de --- /dev/null +++ b/.agents/reports/104_chip_sizing_question_cap/104_chip_sizing_question_cap__04_e2e_suite_commit.a1.validate @@ -0,0 +1,97 @@ +........................................................................ [ 3%] +........................................................................ [ 6%] +........................................................................ [ 10%] +........................................................................ [ 13%] +........................................................................ [ 17%] +........................................................................ [ 20%] +........................................................................ [ 23%] +........................................................................ [ 27%] +........................................................................ [ 30%] +........................................................................ [ 34%] +........................................................................ [ 37%] +........................................................................ [ 41%] +........................................................................ [ 44%] +........................................................................ [ 47%] +........................................................................ [ 51%] +........................................................................ [ 54%] +........................................................................ [ 58%] +........................................................................ [ 61%] +........................................................................ [ 65%] +........................................................................ [ 68%] +........................................................................ [ 71%] +........................................................................ [ 75%] +........................................................................ [ 78%] +........................................................................ [ 82%] +........................................................................ [ 85%] +........................................................................ [ 89%] +........................................................................ [ 92%] +........................................................................ [ 95%] +........................................................................ [ 99%] +.............. [100%] +=============================== warnings summary =============================== +.venv/lib/python3.13/site-packages/fastapi/testclient.py:1 + /var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead. + from starlette.testclient import TestClient as TestClient # noqa + +-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html +================================ tests coverage ================================ +_______________ coverage: platform linux, python 3.13.13-final-0 _______________ + +Name Stmts Miss Cover +-------------------------------------------------- +app/__init__.py 1 0 100% +app/api/__init__.py 0 0 100% +app/api/auth.py 52 0 100% +app/api/chat.py 181 0 100% +app/api/chats.py 110 0 100% +app/api/config.py 13 0 100% +app/api/doc_drafts.py 94 0 100% +app/api/docs.py 134 1 99% +app/api/git_sources.py 229 0 100% +app/api/health.py 10 0 100% +app/api/steering.py 42 0 100% +app/api/suggestions.py 33 0 100% +app/api/sync.py 115 0 100% +app/api/tokens.py 40 0 100% +app/api/ui_settings.py 55 0 100% +app/config.py 140 0 100% +app/core/__init__.py 0 0 100% +app/core/auth.py 45 0 100% +app/core/caching.py 124 0 100% +app/core/debugging.py 29 2 93% +app/core/docs_push.py 39 0 100% +app/core/errors.py 5 0 100% +app/core/logging.py 13 0 100% +app/core/rate_limit.py 44 0 100% +app/core/security_headers.py 20 0 100% +app/core/theming.py 38 0 100% +app/core/tokens.py 44 0 100% +app/db.py 21 0 100% +app/main.py 66 0 100% +app/models.py 125 0 100% +app/rag/__init__.py 0 0 100% +app/rag/agent.py 316 1 99% +app/rag/archive_upload.py 128 0 100% +app/rag/chunker.py 206 4 98% +app/rag/folder_summaries.py 121 0 100% +app/rag/git_sources.py 14 0 100% +app/rag/importer.py 197 3 98% +app/rag/llm.py 243 1 99% +app/rag/overview.py 71 0 100% +app/rag/prompts.py 88 0 100% +app/rag/retriever.py 150 3 98% +app/rag/scaffolding.py 55 0 100% +app/rag/source_removal.py 41 0 100% +app/rag/sources_meta.py 16 0 100% +app/rag/suggestions.py 27 0 100% +app/rag/summarizer.py 24 0 100% +app/schemas.py 307 0 100% +-------------------------------------------------- +TOTAL 3866 15 99% +coverage gate: app/ 99% (>90%) OK +All checks passed! +0 errors, 0 warnings, 0 informations +WARNING: there is a new pyright version available (v1.1.411 -> v1.1.414). +Please install the new version or set PYRIGHT_PYTHON_FORCE_VERSION to `latest` + +validation OK diff --git a/frontend/assets/app.js b/frontend/assets/app.js index 89c1636..cbb8dd4 100644 --- a/frontend/assets/app.js +++ b/frontend/assets/app.js @@ -299,6 +299,7 @@ bindSharedHeaderControls(); const messagesEl = document.querySelector("#messages"); const emptyState = document.querySelector("#empty-state"); const suggestionsEl = document.querySelector("#suggestions"); +const charCountEl = document.querySelector("#char-count"); // phase 104: the question-length counter (ships hidden) const composer = document.querySelector("#composer"); const input = document.querySelector("#message-input"); const sendBtn = document.querySelector("#send-btn"); @@ -1012,6 +1013,7 @@ function appendTruncatedNote(wrap, argument, charsShown, charsTotal) { function submitSuggestion(text) { input.value = text; autoGrow(); + updateCharCount(); // phase 104: the chip fill bypasses maxlength — count what landed input.focus(); composer.requestSubmit(); } @@ -1028,11 +1030,16 @@ function renderChips(container, items, { onSelect } = {}) { btn.className = "suggestion-chip"; btn.setAttribute("role", "listitem"); btn.textContent = text; + // phase 104 (owner 2026-09-12): the single-line chip clips long + // questions — `title` is the hover reveal, `aria-label` the + // clipped-case accessible name (the source-chip pattern). + btn.title = text; btn.addEventListener("click", () => { submitSuggestion(text); if (onSelect) onSelect(text, btn); }); container.appendChild(btn); + if (btn.scrollWidth > btn.clientWidth) btn.setAttribute("aria-label", text); } return container; } @@ -1255,11 +1262,42 @@ export function setUiState(state, errorDetail = "") { if (state === UI_STATE.error) showErrorBanner(errorDetail); } +/* Phase 104 (owner 2026-09-12, A3/A4): the visible question-length cap. + MAX_QUESTION_CHARS mirrors ChatRequest.message max_length=4000 + (app/schemas.py — the source of truth; the server 422s beyond it) and + must stay equal to the #message-input maxlength (cross-file pin in + tests/unit/test_chip_sizing_question_cap.py). The counter shows only + from 80% of the cap — no noise on normal use (owner A4). */ +const MAX_QUESTION_CHARS = 4000; +const CHAR_COUNT_SHOW_AT = 3200; // 80% of the cap — visible only when it matters + function autoGrow() { input.style.height = "auto"; input.style.height = `${Math.min(input.scrollHeight, 192)}px`; } +/* Phase 104 (owner 2026-09-12, A4): the counter state — hidden below the + 80% threshold, plain `len/4000` above it, and `len/4000 — character + limit` + the .is-max (–err-*) treatment at/over the cap. The over-cap + reading is the HONEST length (the programmatic chip-fill path can + exceed maxlength — e.g. `5123/4000 — character limit`). */ +function updateCharCount() { + // RAW length (no trim): raw ≤ cap ⟹ trimmed ≤ cap, so the raw + // count is a safe superset of what the server validates. + const len = input.value.length; + if (len < CHAR_COUNT_SHOW_AT) { + charCountEl.hidden = true; + charCountEl.classList.remove("is-max"); + return; + } + charCountEl.hidden = false; + const atMax = len >= MAX_QUESTION_CHARS; + charCountEl.classList.toggle("is-max", atMax); + charCountEl.textContent = atMax + ? `${len}/${MAX_QUESTION_CHARS} — character limit` + : `${len}/${MAX_QUESTION_CHARS}`; +} + /* ---------- chat turn (SSE streaming, PLAN §4) ---------- */ /* Cancel a response body without leaking an unhandled rejection: @@ -2014,6 +2052,7 @@ function startNewChat() { setUiState(UI_STATE.idle); input.value = ""; autoGrow(); + updateCharCount(); // phase 104: the cleared composer hides the counter again input.focus(); sendStatus.textContent = "New chat started — previous conversation cleared."; } @@ -2111,12 +2150,21 @@ async function handleSend(e) { } const text = input.value.trim(); if (!text || sendBtn.disabled) return; + // Phase 104 (owner 2026-09-12, A5): maxlength caps typing + pastes, but + // a programmatic fill (the chip one-tap path) bypasses it — this guard + // is the never-stale backstop (PLAN §7.4): no turn, no clear, the user + // trims the kept text (out-of-turn banner, the saveAsDoc precedent). + if (text.length > MAX_QUESTION_CHARS) { + showErrorBanner("Questions are limited to 4,000 characters — trim the question and try again."); + return; + } // Phase 49: the user append + persistence save point 1 moved into // runTurn with the rest of the turn — the `reask` flag skips them on // the redo-in-place retry path (the question is already in the DOM + // conversation); handleSend keeps only the form-level pre-work. input.value = ""; autoGrow(); + updateCharCount(); // phase 104: the sent question clears the counter with the input clearErrorBanner(); await runTurn(text, { reask: false }); } @@ -2481,7 +2529,12 @@ async function runTurn(text, { reask = false } = {}) { } } -input.addEventListener("input", autoGrow); +// Phase 104: every input-path change (keystroke, paste — maxlength +// caps both at 4,000) re-runs the counter alongside the auto-grow. +input.addEventListener("input", () => { + autoGrow(); + updateCharCount(); +}); input.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); diff --git a/frontend/assets/styles.css b/frontend/assets/styles.css index 323e694..8cd153e 100644 --- a/frontend/assets/styles.css +++ b/frontend/assets/styles.css @@ -703,14 +703,6 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; } gap: 0.45rem; padding-inline: 0.25rem; } -/* As flex items these chips must be allowed to shrink (min-width:auto - would let a long title-derived chip exceed the column on phones — - phase 07 overflow fix); the label text then wraps inside the pill. */ -.maybe-try .suggestion-chip { - min-width: 0; - max-width: 100%; -} - /* ---------- Steering notes (phase 15) ---------- */ /* "Tune" button in the meta row of every completed brain bubble: ghost pill, ≥44px, right-aligned after the source chips. ink-soft on surface @@ -1230,6 +1222,21 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; } margin: 0; padding: 0; } +/* The shared chip pill — both chip rows (the onboarding row in the + empty state and the "Maybe try" row under a deflected bubble) reuse + it. Phase 104 (owner 2026-09-12, A1): a chip is ONE line at every + viewport width — `white-space: nowrap` stops a 400-char question + from wrapping the pill into a multi-line "chonk"; `overflow: hidden` + zeroes the flex item's automatic minimum size (min-width:auto would + pin the item to the full text width), so `max-width: 100%` binds and + `text-overflow: ellipsis` clips the text at the row edge — in the + desktop wrap row 100% is the chat column, in the ≤640px row + (nowrap + overflow-x: auto) 100% is the VISIBLE width and the row + scrolls. That clipping is the phase-07 overflow fix the old + "Maybe try" chip override used to carry (min-width:0 + max-width: + 100%) — now fully subsumed here and deleted, provenance folded into + this comment so the history lives with the contract. The full text + stays one hover away (the title tooltip, phase 104 task 02). */ .suggestion-chip { font: inherit; font-size: 0.92rem; @@ -1242,6 +1249,11 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; } min-height: 44px; cursor: pointer; transition: background 0.15s ease, transform 0.05s ease; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + min-width: 0; } .suggestion-chip:hover { background: var(--brand-soft); } .suggestion-chip:active { transform: scale(0.98); } @@ -1321,6 +1333,17 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; } #view-chat:not(.chat-booted) .composer { position: static; } +/* Phase 104 (owner 2026-09-12, A4): the question-length counter — + right-aligned above the composer, a child of the .chat-bottom sticky + unit (between the actions row and the form), hidden until 80% of the + 4,000-char cap (app.js updateCharCount). It sits on the APP + background behind the transparent .chat-bottom unit, so both pairings + are verified against --bg: --ink-soft on --bg = 8.6:1 (≥4.5:1, WCAG + AA) and the .is-max state's --err-ink on --bg = 10.4:1 (AA). The + .is-max state ALSO changes the copy ("— character limit") — text + + color, never color alone (B3). */ +.char-count { margin: 0; text-align: right; font-size: 0.75rem; line-height: 1.2; color: var(--ink-soft); } +.char-count.is-max { color: var(--err-ink); } .composer { display: flex; align-items: flex-end; diff --git a/frontend/index.html b/frontend/index.html index 4925500..c8d9f69 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -252,6 +252,15 @@ + + +
+