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:
@@ -130,8 +130,24 @@ def _assert_no_doc_overflow(page: Page, label: str) -> None:
|
||||
|
||||
|
||||
def _tab_outline_walk(page: Page, max_tabs: int = 60) -> list[dict[str, str]]:
|
||||
"""Real keyboard Tab walk; returns each focused element's outline."""
|
||||
first_key: str | None = None
|
||||
"""Real keyboard Tab walk; returns each focused element's outline.
|
||||
|
||||
The walk covers a FULL focus cycle (it starts wherever Chromium
|
||||
resumes after the skip-link check — mid-document, right after
|
||||
#main — and ends when focus wraps back to the FIRST element it
|
||||
visited). The wrap is detected by TRUE element identity: each
|
||||
newly focused element is stamped with a unique ``data-tabwalk-id``
|
||||
and the cycle ends when a STAMPED element is focused again.
|
||||
Phase 104 (task 04, 2026-09-12): the pre-104 heuristic keyed on
|
||||
the first 24 chars of the element's text — two chips sharing that
|
||||
prefix (e.g. the two identical "How is my Kubernetes cluster set
|
||||
up? write a long answer" opener chips left in ``saved_chats`` by
|
||||
test_pinned_composer.py, which runs suites in sequence on the
|
||||
shared e2e DB) collided and cut the walk short at two entries,
|
||||
flaking ``len(seen) >= 3``. Verified pre-existing on the
|
||||
pre-phase-104 tree (the mid-document Tab start is Chromium
|
||||
behavior, not a phase-104 change)."""
|
||||
first_id: str | None = None
|
||||
seen: list[dict[str, str]] = []
|
||||
for _ in range(max_tabs):
|
||||
page.keyboard.press("Tab")
|
||||
@@ -142,7 +158,13 @@ def _tab_outline_walk(page: Page, max_tabs: int = 60) -> list[dict[str, str]]:
|
||||
const cls = String(el.className).split(" ")[0];
|
||||
const label = (el.getAttribute("aria-label")
|
||||
|| el.textContent || "").trim().slice(0, 24);
|
||||
let id = el.getAttribute("data-tabwalk-id");
|
||||
if (!id) {
|
||||
id = "tw" + ((window.__twSeq = (window.__twSeq || 0) + 1));
|
||||
el.setAttribute("data-tabwalk-id", id);
|
||||
}
|
||||
return {
|
||||
id: id,
|
||||
key: el.tagName + "#" + (el.id || "") + "." + cls + ":" + label,
|
||||
outline_style: cs.outlineStyle,
|
||||
outline_width: cs.outlineWidth,
|
||||
@@ -151,11 +173,11 @@ def _tab_outline_walk(page: Page, max_tabs: int = 60) -> list[dict[str, str]]:
|
||||
)
|
||||
if info["key"].startswith("BODY"):
|
||||
continue # focus has not entered the document yet
|
||||
if first_key is None:
|
||||
first_key = info["key"]
|
||||
if first_id is None:
|
||||
first_id = info["id"]
|
||||
seen.append(info)
|
||||
if len(seen) > 1 and info["key"] == first_key:
|
||||
break # wrapped back to the first focusable
|
||||
if len(seen) > 1 and info["id"] == first_id:
|
||||
break # wrapped back to the first focusable (same ELEMENT)
|
||||
return seen
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user