Skip to content

Commit ffd931e

Browse files
fix: make tutor chat close X actually close the panel (#10)
Root cause: .tutor-chat__panel sets `display: flex`, which has the same specificity as the UA stylesheet's `[hidden] { display: none }` rule. Because author stylesheets win cascade ties over UA, the `hidden` attribute was a no-op and the panel never actually hid when the X was clicked. Changes - tutor-chat.css: add `.tutor-chat__panel[hidden] { display: none }` so the hidden attribute reliably hides the panel; bump panel z-index to 61 so it is unambiguously above the FAB (both were 60). - tutor-chat.js: add `type="button"` to FAB and close to keep behavior predictable; make the close SVG non-interactive (pointer-events="none", focusable="false") so the click target is always the button; call preventDefault on both, and stopPropagation on the close click. - index.html + sw.js: bump cache-busting versions so the SW picks up the fixed assets. - tests: two new static checks asserting the regression cannot return: the [hidden] CSS rule must be present, and the close button must be wired with toggle(false, ...). Co-authored-by: Claude Code <claude-code@anthropic.com>
1 parent 9341eb1 commit ffd931e

5 files changed

Lines changed: 42 additions & 9 deletions

File tree

backend/tests/test_frontend_integration.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,26 @@ def test_index_html_references_chat_assets() -> None:
8282
assert 'meta name="tutor-backend"' in index
8383

8484

85+
def test_chat_panel_hidden_attribute_works() -> None:
86+
"""Regression: the panel's `display: flex` overrides the UA stylesheet's
87+
`[hidden] { display: none }` rule because they share specificity, so
88+
clicking the close X never actually hid the panel. The CSS must re-assert
89+
`display: none` for the hidden state."""
90+
css = (FRONTEND_DIR / "tutor-chat.css").read_text(encoding="utf-8")
91+
assert re.search(
92+
r"\.tutor-chat__panel\[hidden\]\s*\{[^}]*display\s*:\s*none",
93+
css,
94+
), "tutor-chat.css must re-assert display:none for .tutor-chat__panel[hidden]"
95+
96+
97+
def test_chat_close_button_is_wired() -> None:
98+
"""The close X must have a click handler that calls toggle(false, ...)."""
99+
js = (FRONTEND_DIR / "tutor-chat.js").read_text(encoding="utf-8")
100+
assert "tutorChatClose" in js
101+
assert "closeBtn.addEventListener('click'" in js or 'closeBtn.addEventListener("click"' in js
102+
assert "toggle(false" in js
103+
104+
85105
def test_chat_js_posts_to_api_chat() -> None:
86106
js = (FRONTEND_DIR / "tutor-chat.js").read_text(encoding="utf-8")
87107
# The module must POST to /api/chat with a JSON body containing `messages`.

frontend/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979

8080
<link rel="stylesheet" href="base.css?v=20260416c" />
8181
<link rel="stylesheet" href="style.css?v=20260416b" />
82-
<link rel="stylesheet" href="tutor-chat.css?v=20260516a" />
82+
<link rel="stylesheet" href="tutor-chat.css?v=20260519a" />
8383
<link rel="stylesheet" href="tutor-codelab.css?v=20260516a" />
8484

8585
<!--
@@ -269,7 +269,7 @@ <h1 class="section__title" id="secTitle">Title</h1>
269269

270270
<script src="tutor-codelab.js?v=20260516a" defer></script>
271271
<script src="app.js?v=20260416" defer></script>
272-
<script src="tutor-chat.js?v=20260516a" defer></script>
272+
<script src="tutor-chat.js?v=20260519a" defer></script>
273273

274274
<!-- Service Worker Registration + Cache Bust -->
275275
<script>

frontend/sw.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Cache-first for shell, network-first for content
44
============================================================ */
55

6-
const CACHE_VERSION = 'pytutor-v2026-05-16d';
6+
const CACHE_VERSION = 'pytutor-v2026-05-19a';
77
const SHELL_ASSETS = [
88
'./',
99
'./index.html',

frontend/tutor-chat.css

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,15 @@
4646
border-radius: var(--r-lg);
4747
box-shadow: var(--shadow-pop);
4848
overflow: hidden;
49-
z-index: 60;
49+
z-index: 61;
5050
}
5151

52+
/* The class above sets display:flex, which has the same specificity as the UA
53+
rule [hidden] { display: none }. Author rules win the cascade, so the
54+
hidden attribute would otherwise be a no-op and the panel would never
55+
close. Re-assert display:none for the hidden state. */
56+
.tutor-chat__panel[hidden] { display: none; }
57+
5258
@media (max-width: 559px) {
5359
.tutor-chat__panel {
5460
left: var(--sp-3);

frontend/tutor-chat.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119
root.id = 'tutorChatRoot';
120120
root.className = 'tutor-chat';
121121
root.innerHTML = `
122-
<button class="tutor-chat__fab" id="tutorChatFab" aria-label="Open tutor chat" aria-expanded="false" aria-controls="tutorChatPanel">
122+
<button type="button" class="tutor-chat__fab" id="tutorChatFab" aria-label="Open tutor chat" aria-expanded="false" aria-controls="tutorChatPanel">
123123
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
124124
<path d="M5 5h14a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H9l-4 4V7a2 2 0 0 1 2-2Z"
125125
fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
@@ -134,8 +134,8 @@
134134
<h2 class="tutor-chat__title">Ask about Python</h2>
135135
<p class="tutor-chat__sub" id="tutorChatSub">Connecting…</p>
136136
</div>
137-
<button class="tutor-chat__close" id="tutorChatClose" aria-label="Close tutor chat">
138-
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
137+
<button type="button" class="tutor-chat__close" id="tutorChatClose" aria-label="Close tutor chat" title="Close tutor chat">
138+
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false" pointer-events="none">
139139
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
140140
</svg>
141141
</button>
@@ -174,8 +174,15 @@
174174
const banner = root.querySelector('#tutorChatBanner');
175175
const sendBtn = root.querySelector('#tutorChatSend');
176176

177-
fab.addEventListener('click', () => toggle(true, { panel, fab, input }));
178-
closeBtn.addEventListener('click', () => toggle(false, { panel, fab, input }));
177+
fab.addEventListener('click', (e) => {
178+
e.preventDefault();
179+
toggle(true, { panel, fab, input });
180+
});
181+
closeBtn.addEventListener('click', (e) => {
182+
e.preventDefault();
183+
e.stopPropagation();
184+
toggle(false, { panel, fab, input });
185+
});
179186
resetBtn.addEventListener('click', () => {
180187
state.history = [];
181188
renderLog(log);

0 commit comments

Comments
 (0)