phase: 104_chip_sizing_question_cap
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`.
This commit is contained in:
@@ -312,12 +312,13 @@ def test_chat_bottom_unit_is_last_child_of_the_chat_shell() -> None:
|
||||
"""Phase 65 (task 02, owner-locked A1): the LAST element child of
|
||||
`.chat-shell` is the `.chat-bottom` wrapper — NO id (nothing in JS
|
||||
binds it; the bindings live on the inner elements, the move is pure
|
||||
HTML/CSS) — holding exactly the `.chat-actions` row and then the
|
||||
`#composer` form, in that order: the row + composer are ONE sticky
|
||||
unit, and the wrapper owns the shell's bottom slot, so the sticky
|
||||
shift range is still that column's box (a sibling after it would
|
||||
carve the range away and re-break the pin). The composer form keeps
|
||||
`novalidate` and its contract ids."""
|
||||
HTML/CSS) — holding the `.chat-actions` row, the phase-104
|
||||
`#char-count` counter, and the `#composer` form, in that order: the
|
||||
row + counter + composer are ONE sticky unit, and the wrapper owns
|
||||
the shell's bottom slot, so the sticky shift range is still that
|
||||
column's box (a sibling after it would carve the range away and
|
||||
re-break the pin). The composer form keeps `novalidate` and its
|
||||
contract ids."""
|
||||
shell = _tree().find("chat-shell")
|
||||
last = shell["children"][-1]
|
||||
assert last["tag"] == "div" and (
|
||||
@@ -330,17 +331,29 @@ def test_chat_bottom_unit_is_last_child_of_the_chat_shell() -> None:
|
||||
"elements"
|
||||
)
|
||||
kids = last["children"]
|
||||
assert len(kids) == 2, (
|
||||
"the unit holds exactly two element children: .chat-actions, then "
|
||||
"#composer"
|
||||
assert len(kids) == 3, (
|
||||
"the unit holds exactly three element children: .chat-actions, "
|
||||
"then #char-count (phase 104), then #composer"
|
||||
)
|
||||
row, form = kids
|
||||
row, counter, form = kids
|
||||
assert row["tag"] == "div" and (
|
||||
row["attrs"].get("class") or ""
|
||||
).split() == ["chat-actions"], (
|
||||
"the first child is the .chat-actions row (the New chat → Share "
|
||||
"DOM order is pinned by test_save_chat_ui.py)"
|
||||
)
|
||||
# Phase 104 (owner 2026-09-12): the question-length counter — a
|
||||
# hidden-by-default <p> between the row and the composer (zero
|
||||
# height while hidden; a new child INSIDE the unit breaks no pin —
|
||||
# the sticky geometry below is untouched).
|
||||
assert counter["tag"] == "p" and counter["attrs"].get("id") == "char-count", (
|
||||
"the second child is the phase-104 #char-count counter"
|
||||
)
|
||||
assert (counter["attrs"].get("class") or "") == "char-count"
|
||||
assert "hidden" in counter["attrs"], (
|
||||
"the counter ships hidden — it appears only from 80% of the "
|
||||
"4,000-char cap (app.js updateCharCount)"
|
||||
)
|
||||
assert form["tag"] == "form" and form["attrs"].get("id") == "composer"
|
||||
assert "novalidate" in form["attrs"], (
|
||||
"phase 48: the composer form stays `novalidate` (a `required` "
|
||||
|
||||
Reference in New Issue
Block a user