fix(chat): render typing-indicator elapsed hint as horizontal text
The phase-87 ".typing-elapsed" dot-geometry reset (specificity 0,1,0) lost every shared declaration to the ".typing span" dot rule (0,1,1): the hint rendered as an 8x8px bouncing dot and the "Ns" text wrapped one character per line below the bubble (overflow-wrap: anywhere on .bubble). Phase 87's e2e checked text values only, so the squish shipped unseen. - retarget the reset at ".typing span.typing-elapsed" (0,2,1) so it actually wins; center the dots while the hint line is taller - unit: pin the reset's selector context (specificity regression guard) - e2e: layout pin on the live hint — no dot animation, not an 8px box, horizontal single-line bounding box - before/after verification screenshots in .agents/reports/87_big_read_progress/ Verified: unit 41 passed, phase-87 e2e 4 passed (isolated), ruff + pyright clean.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1110,7 +1110,10 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
}
|
||||
|
||||
/* typing indicator */
|
||||
.typing { display: inline-flex; gap: 5px; padding: 0.9rem 1rem; }
|
||||
/* align-items: center — the dots stay vertically centered when the
|
||||
phase-87 "Ns" hint (a full text line) makes the row taller; with
|
||||
only the three 8px dots the centering is a visual no-op. */
|
||||
.typing { display: inline-flex; align-items: center; gap: 5px; padding: 0.9rem 1rem; }
|
||||
.typing span {
|
||||
width: 8px; height: 8px;
|
||||
border-radius: 50%;
|
||||
@@ -1129,8 +1132,14 @@ details.thinking .thinking-text ul { margin: 0 0 0.5rem; }
|
||||
ink-soft on the bubble surface (the AA pairing), small mono like
|
||||
every status line. Plain text: no animation, no motion opt-out.
|
||||
The span is a sibling of the dots inside the same .typing bubble, so
|
||||
it resets the dot geometry (.typing span) it would otherwise inherit. */
|
||||
.typing-elapsed {
|
||||
it resets the dot geometry (.typing span) it would otherwise inherit.
|
||||
The selector MUST stay ".typing span.typing-elapsed": a bare
|
||||
".typing-elapsed" (0,1,0) LOSES the specificity war to the dot rule
|
||||
".typing span" (0,1,1) — every reset below (width/height/border-
|
||||
radius/background/opacity/animation) would be overridden and the
|
||||
hint would render as an 8×8px bouncing dot with the "Ns" text
|
||||
wrapping one character per line (phase-87 regression, 2026-09). */
|
||||
.typing span.typing-elapsed {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 0;
|
||||
|
||||
@@ -567,7 +567,15 @@ def test_typing_indicator_shows_visible_elapsed(
|
||||
the screen-reader channel the unit pins protect) read together at
|
||||
the same moment; a second value sample ≥1.5 s later is strictly
|
||||
greater (the clock ticks while the gap holds — the next frame is
|
||||
≥6 s away)."""
|
||||
≥6 s away).
|
||||
|
||||
GEOMETRY GUARD (2026-09, phase-87 fix): the hint must render as a
|
||||
single horizontal text line inline with the dots — NOT as the 8×8px
|
||||
bouncing dot it became when the bare ``.typing-elapsed`` selector
|
||||
lost the specificity war to the ``.typing span`` dot rule (the
|
||||
"Ns" text then wrapped one character per line below the bubble).
|
||||
Phase 87's original run checked text values only, so the squish
|
||||
shipped unseen; this assertion is the layout pin that was missing."""
|
||||
page.set_default_timeout(30_000)
|
||||
_reset_db(_seed)
|
||||
_submit_tools_turn(page, app_url)
|
||||
@@ -580,6 +588,29 @@ def test_typing_indicator_shows_visible_elapsed(
|
||||
v1 = _wait_visible_hint_and_aria(page, 10, TYPING_HINT_TIMEOUT_MS)
|
||||
assert v1 >= 10, f"the hint appeared below the 10 s gate: {v1}s"
|
||||
|
||||
# Layout pin: the hint is a horizontal text line, not the 8×8px
|
||||
# dot it degrades to when the dot-geometry reset loses the
|
||||
# specificity war (width 8px + overflow-wrap: anywhere then wraps
|
||||
# the "Ns" one character per line, spilling below the bubble).
|
||||
geom = page.locator("#typing-indicator .typing-elapsed").first.evaluate(
|
||||
"""e => {
|
||||
const s = getComputedStyle(e);
|
||||
const r = e.getBoundingClientRect();
|
||||
return {anim: s.animationName, w: s.width, h: s.height,
|
||||
bw: r.width, bh: r.height};
|
||||
}"""
|
||||
)
|
||||
assert geom["anim"] == "none", (
|
||||
f"the hint must be plain text, not the dot animation: {geom}"
|
||||
)
|
||||
assert geom["w"] != "8px" and geom["h"] != "8px", (
|
||||
f"the hint must not be an 8px dot: {geom}"
|
||||
)
|
||||
assert geom["bw"] >= 16 and geom["bw"] > geom["bh"], (
|
||||
"the 'Ns' text must run HORIZONTALLY (one line ≥3ch wide), not "
|
||||
f"wrap one character per line: {geom}"
|
||||
)
|
||||
|
||||
# The clock ticks while the gap holds: ≥1.5 s later the value is
|
||||
# strictly greater (the read frame is ≥6 s away; the first delta
|
||||
# — which removes the whole indicator — is ≈25 s from submit).
|
||||
|
||||
@@ -111,10 +111,21 @@ def test_typing_elapsed_css_rule_is_the_aa_pairing() -> None:
|
||||
language as every status line. Plain text — ``animation: none`` +
|
||||
no background, and the dot-geometry reset (the span is a sibling of
|
||||
the dots inside the .typing bubble, so without the reset it would
|
||||
render as an 8px bouncing dot, not a hint)."""
|
||||
render as an 8px bouncing dot, not a hint).
|
||||
|
||||
The reset's SELECTOR is pinned too (phase-87 regression, 2026-09):
|
||||
a bare ``.typing-elapsed`` (specificity 0,1,0) loses every shared
|
||||
declaration to the dot rule ``.typing span`` (0,1,1) — the hint
|
||||
then rendered as an 8×8px bouncing dot with the "Ns" text wrapping
|
||||
one character per line below the bubble. The reset must carry the
|
||||
span context (``.typing span.typing-elapsed``, 0,2,1) to win."""
|
||||
css = _css()
|
||||
block = re.search(r"\.typing-elapsed \{([\s\S]*?)\n\}", css)
|
||||
assert block, "styles.css must style .typing-elapsed"
|
||||
assert ".typing span.typing-elapsed {" in css, (
|
||||
"the dot-geometry reset must target the span in .typing context "
|
||||
"(bare .typing-elapsed loses the specificity war to .typing span)"
|
||||
)
|
||||
block = re.search(r"\.typing span\.typing-elapsed \{([\s\S]*?)\n\}", css)
|
||||
assert block, "styles.css must style .typing span.typing-elapsed"
|
||||
body = block.group(1)
|
||||
for prop in (
|
||||
"font-family: var(--mono)",
|
||||
|
||||
Reference in New Issue
Block a user