fix: Crisis overlay keyboard navigation — Escape key + focus recovery (#95) #104
Closed
Rockachopa
wants to merge 3 commits from
fix/95-crisis-overlay-keyboard-nav into main
pull from: fix/95-crisis-overlay-keyboard-nav
merge into: Timmy_Foundation:main
Timmy_Foundation:main
Timmy_Foundation:fix/200
Timmy_Foundation:step35/201-intake-submission-from-image
Timmy_Foundation:step35/203-intake-submission-from-image
Timmy_Foundation:step35/200-intake-submission-from-test
Timmy_Foundation:step35/197-door-1-build-anonymous-intake
Timmy_Foundation:fix/121
Timmy_Foundation:fix/99
Timmy_Foundation:fix/130
Timmy_Foundation:fix/141-crisis-contract
Timmy_Foundation:burn/20260421-0040-fix
Timmy_Foundation:fix/101
Timmy_Foundation:fix/134
Timmy_Foundation:fix/135
Timmy_Foundation:fix/100
Timmy_Foundation:fix/123
Timmy_Foundation:fix/133
Timmy_Foundation:fix/36
Timmy_Foundation:fix/37
Timmy_Foundation:fix/96
Timmy_Foundation:fix/41
Timmy_Foundation:fix/673-genome-door
Timmy_Foundation:fix/95
Timmy_Foundation:fix/97
Timmy_Foundation:fix/73-toast-alerts
Timmy_Foundation:fix/131-voice-analysis
Timmy_Foundation:fix/59-about-footer-link
Timmy_Foundation:fix/59-1776403637
Timmy_Foundation:burn/59-1776304777
Timmy_Foundation:fix/59-about-link
Timmy_Foundation:fix/136-crisis-metrics-cli
Timmy_Foundation:feat/136-cli-metrics
Timmy_Foundation:fix/38
Timmy_Foundation:fix/75
Timmy_Foundation:fix/673
Timmy_Foundation:door/issue-38
Timmy_Foundation:fix/141
Timmy_Foundation:fix/59
Timmy_Foundation:burn/73-inline-toast
Timmy_Foundation:burn/1601-crisis-logic-alignment
Timmy_Foundation:burn/123-1776303473
Timmy_Foundation:burn/73-1776303595
Timmy_Foundation:burn/123-1776303595
Timmy_Foundation:fix/130-behavioral
Timmy_Foundation:feat/136-crisis-metrics-cli
Timmy_Foundation:feat/134-unified-crisis-scorer
Timmy_Foundation:feat/133-behavioral-detection
Timmy_Foundation:feat/131-voice-analysis
Timmy_Foundation:burn/35-1776218265
Timmy_Foundation:fix/132
Timmy_Foundation:burn/99-1776264183
Timmy_Foundation:burn/99-1776264262
Timmy_Foundation:fix/136
Timmy_Foundation:fix/123-duplicate-patterns-6213
Timmy_Foundation:fix/burn-crisis-logic-alignment
Timmy_Foundation:burn/37-1776264184
Timmy_Foundation:burn/101-1776264183
Timmy_Foundation:burn/69-1776264183
Timmy_Foundation:burn/101-1776264262
Timmy_Foundation:burn/38-1776264184
Timmy_Foundation:burn/41-1776264184
Timmy_Foundation:burn/36-1776264184
Timmy_Foundation:burn/101-1776263878
Timmy_Foundation:door/issue-35
Timmy_Foundation:door/issue-69
Timmy_Foundation:fix/101-crisis-ab-testing
Timmy_Foundation:fix/94-safety-plan-inline-feedback
Timmy_Foundation:feat/safety-plan-history
Timmy_Foundation:fix/96-safety-plan-version-history
Timmy_Foundation:fix/97-crisis-metrics-endpoint
Timmy_Foundation:fix/98-offline-crisis-resources
Timmy_Foundation:fix/crisis-overlay-debounce
Timmy_Foundation:fix/90-overlay-focus-enabled-element
Timmy_Foundation:fix/95-overlay-keyboard-nav
Timmy_Foundation:fix/59-footer-about-link
Timmy_Foundation:fix/overlay-escape-key
Timmy_Foundation:burn/73-1776218082
Timmy_Foundation:burn/73-1776218180
Timmy_Foundation:burn/36-1776218258
Timmy_Foundation:fix/38-safety-plan-in-chat
Timmy_Foundation:burn/69-1776218206
Timmy_Foundation:burn/69-1776217968
Timmy_Foundation:burn/37-1776217996
Timmy_Foundation:burn/73-1776217949
Timmy_Foundation:door/issue-73
Timmy_Foundation:burn/41-1776217979
Timmy_Foundation:fix/issue-75-1
Timmy_Foundation:fix/issue-73-2
Timmy_Foundation:fix/67-focus-trap
Timmy_Foundation:fix/65-modal-focus-trap
Timmy_Foundation:fix/69-initial-focus
Timmy_Foundation:fix/59-about-route
Timmy_Foundation:fix/59-footer-about
Timmy_Foundation:dispatch/38-1776180746
Timmy_Foundation:dispatch/40-1776180746
Timmy_Foundation:am/38-1776166469
Timmy_Foundation:am/40-1776166469
Timmy_Foundation:am/41-1776166469
Timmy_Foundation:dispatch/41-1776180746
Timmy_Foundation:dawn/38-1776130053
Timmy_Foundation:triage/38-1776129677
Timmy_Foundation:q/38-1776129480
Timmy_Foundation:queue/38-1776129201
Timmy_Foundation:burn/59-1776131200
Timmy_Foundation:burn/37-1776131000
Timmy_Foundation:dawn/40-1776130053
Timmy_Foundation:dawn/41-1776130053
Timmy_Foundation:triage/40-1776129677
Timmy_Foundation:triage/41-1776129677
Timmy_Foundation:q/41-1776129480
Timmy_Foundation:q/40-1776129480
Timmy_Foundation:queue/41-1776129201
Timmy_Foundation:queue/40-1776129201
Timmy_Foundation:whip/38-1776128804
Timmy_Foundation:whip/40-1776128804
Timmy_Foundation:whip/41-1776128804
Timmy_Foundation:feat/session-crisis-tracking
Timmy_Foundation:burn/20260413-1620-dying-detection-dedup
Timmy_Foundation:feat/crisis-synthesizer
Timmy_Foundation:feat/compassion-router-wiring
Timmy_Foundation:fix/deduplicate-crisis-detectors
Timmy_Foundation:feat/compassion-router-gateway
Timmy_Foundation:fix/dedup-crisis-detector
Timmy_Foundation:fix/remove-bridge-false-positive
Timmy_Foundation:burn/20260413-0406-fix
Timmy_Foundation:burn/rescue-crisis
Timmy_Foundation:burn/20260413-0213-vps-deploy
Timmy_Foundation:fix/test-none-input
Timmy_Foundation:burn/20260412-0812-vps-prep
Timmy_Foundation:burn/20260412-1851-crisis-system
Timmy_Foundation:burn/20260409-1931-crisis-prompt
Timmy_Foundation:burn/20260410-2030-crisis-active-listening
Timmy_Foundation:fix/add-smoke-test
Timmy_Foundation:feat/ci-sanity-checks
Timmy_Foundation:feat/detailed-crisis-guidelines
Timmy_Foundation:burn/20260409-1230-issue4-crisis-backend
Timmy_Foundation:feat/sovereign-heart-router
Timmy_Foundation:feature/resilience
Timmy_Foundation:feature/content-pages
Timmy_Foundation:feature/dying-detection
Timmy_Foundation:feature/crisis-system-prompt
Labels
Clear labels
backend
batch-pipeline
content
deploy
epic
frontend
hardening
infra
priority:critical
priority:high
priority:medium
protocol
throughput-10x
token-masterplan
velocity-engine
Backend / API
batch-pipeline label
Content, copy, testimony
Deployment / go-live
Epic / parent issue
Frontend / UI
Resilience, fallback, security
Infrastructure, DevOps, deployment
Must have for launch
Important for launch
Nice to have at launch
Crisis detection protocol
throughput-10x label
token-masterplan label
Auto-generated by velocity engine
No Label
Milestone
No items
No Milestone
Projects
Clear projects
No project
Assignees
Rockachopa
Timmy
allegro
antigravity
bezalel
claude
codex-agent
ezra
gemini
google
grok
hermes
kimi
manus
perplexity
sonnet
Clear assignees
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Timmy_Foundation/the-door#104
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "fix/95-crisis-overlay-keyboard-nav"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What changed
Crisis overlay keyboard navigation fix (issue #95)
Problem
Fix (index.html —
trapFocusInOverlay)Escape key handling: Press Escape to close the overlay (when dismiss button is enabled). Mirrors the safety plan modal's Escape behavior.
Focus recovery: If focus somehow leaves the overlay, the next Tab press brings it back to the first focusable element instead of letting it wander into the background.
Existing behavior preserved: Tab wrapping (last→first) and Shift+Tab wrapping (first→last) unchanged.
Tests
Updated
tests/test_crisis_overlay_focus_trap.pywith 2 new static tests:test_overlay_escape_key_closes— verifies Escape handler exists in HTMLtest_overlay_focus_recovery— verifies focus recovery logic existsNew
tests/test_crisis_overlay_keyboard.py— Playwright E2E tests:Acceptance criteria
Closes #95
Fix looks reasonable.
Scope: 3 file(s) changed (251+ / 0-)
Comprehensive fix for crisis overlay keyboard navigation (issue #95). The Escape key handler and focus recovery logic are correct.
Issues:
Focus recovery uses linear scan instead of
contains(): The focus recovery code iterates through all focusable elements to check ifdocument.activeElementis inside the overlay. PR #116 uses the cleanercrisisOverlay.contains(document.activeElement)approach. If this PR merges after #116, there will be a merge conflict. Recommend aligning with thecontains()approach.Escape key does nothing when dismiss button is disabled: During the countdown period, pressing Escape is silently swallowed (
e.preventDefault()runs but no action occurs). This is intentional per the design (force users to see crisis resources), but consider providing user feedback — e.g., briefly flash the countdown timer or show a tooltip explaining why Escape is disabled.Playwright test has syntax issues: In
test_escape_closes_overlay, the lineexpect(overlay).to_have_class(/active/)uses JavaScript regex syntax, not Python. This should beexpect(overlay).to_have_class(re.compile(r"active"))or use a string matcher. Same issue intest_escape_returns_focus_to_chat_input. Thetest_focus_does_not_escape_overlaytest also has a multi-line assert with backslash continuation that will likely fail.Port collision risk: The Playwright test server uses hardcoded port 18923. If another test or process uses this port, the test fails silently. Consider using port 0 (random available port).
_trigger_crisis_overlaybypasses the countdown: The test helper enables the dismiss button and focuses it directly, which does not match real user flow. The Escape key test passes because the button is pre-enabled, but in production the button starts disabled.Item 3 (Playwright syntax errors) will cause test failures — must be fixed.
Review: fix: Crisis overlay keyboard navigation — Escape key + focus recovery
Good implementation of Escape key dismissal and focus recovery. The Playwright E2E tests are a nice addition.
Focus recovery uses a loop over
focusableelements to check ifactiveElementis inside — PR #116 does this more cleanly withcrisisOverlay.contains(document.activeElement). Prefer that approach.Overlaps significantly with PRs #116, #126, and #84 — all fix the same issue (#69/#95). Coordinate to merge only one.
The Playwright test expects
/active/regex — this is fine in Playwright but verify it matches the exact class pattern.test_focus_does_not_escape_overlayhas a long assertion on line 284 — break it up for readability.Closing: stale PR, superseded by newer mergeable implementation.
Pull request closed