Compare commits

..

2 Commits

Author SHA1 Message Date
b6af9ca9db fix: Footer /about link points to missing route (#59)
All checks were successful
Sanity Checks / sanity-test (pull_request) Successful in 6s
Smoke Test / smoke (pull_request) Successful in 14s
2026-04-17 05:27:51 +00:00
56f991d615 fix: Footer /about link points to missing route (#59) 2026-04-17 05:27:49 +00:00
3 changed files with 76 additions and 115 deletions

View File

@@ -681,7 +681,6 @@ html, body {
<!-- Footer -->
<footer id="footer">
<a href="/about.html" aria-label="About The Door">about</a>
<button id="crisis-resources-btn" aria-label="Open crisis resources">crisis resources</button>
<button id="safety-plan-btn" aria-label="Open My Safety Plan">my safety plan</button>
<button id="clear-chat-btn" aria-label="Clear chat history">clear chat</button>
</footer>
@@ -812,7 +811,6 @@ Sovereignty and service always.`;
var overlayCallLink = document.querySelector('.overlay-call');
var statusDot = document.querySelector('.status-dot');
var statusText = document.getElementById('status-text');
var crisisResourcesBtn = document.getElementById('crisis-resources-btn');
// Safety Plan Elements
var safetyPlanBtn = document.getElementById('safety-plan-btn');
@@ -828,9 +826,6 @@ Sovereignty and service always.`;
var isStreaming = false;
var overlayTimer = null;
var crisisPanelShown = false;
var CRISIS_OVERLAY_COOLDOWN_MS = 10 * 60 * 1000;
var CRISIS_OVERLAY_LAST_SHOWN_KEY = 'timmy_crisis_overlay_last_shown_at';
var CRISIS_OVERLAY_EVENT_LOG_KEY = 'timmy_crisis_overlay_event_log';
// ===== SERVICE WORKER =====
if ('serviceWorker' in navigator) {
@@ -858,43 +853,6 @@ Sovereignty and service always.`;
window.addEventListener('offline', updateOnlineStatus);
updateOnlineStatus();
function getLastOverlayShownAt() {
try {
return parseInt(localStorage.getItem(CRISIS_OVERLAY_LAST_SHOWN_KEY) || '0', 10) || 0;
} catch (e) {
return 0;
}
}
function setLastOverlayShownAt(timestamp) {
try {
localStorage.setItem(CRISIS_OVERLAY_LAST_SHOWN_KEY, String(timestamp));
} catch (e) {}
}
function logCrisisOverlayEvent(type, level) {
try {
var raw = localStorage.getItem(CRISIS_OVERLAY_EVENT_LOG_KEY);
var events = raw ? JSON.parse(raw) : [];
if (!Array.isArray(events)) events = [];
events.push({ type: type, level: level, at: Date.now() });
if (events.length > 20) events = events.slice(events.length - 20);
localStorage.setItem(CRISIS_OVERLAY_EVENT_LOG_KEY, JSON.stringify(events));
} catch (e) {}
}
function openCrisisResources() {
crisisPanelShown = true;
crisisPanel.classList.add('visible');
if (typeof crisisPanel.scrollIntoView === 'function') {
crisisPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
var firstAction = crisisPanel.querySelector('.crisis-btn, a[href]');
if (firstAction && typeof firstAction.focus === 'function') {
firstAction.focus();
}
}
// ===== CRISIS KEYWORDS =====
// Tier 1: General crisis indicators - triggers enhanced 988 panel
var crisisKeywords = [
@@ -1063,19 +1021,6 @@ Sovereignty and service always.`;
var _preOverlayFocusElement = null;
function showOverlay() {
return showOverlayWithRateLimit(false, 2);
}
function showOverlayWithRateLimit(forceOpen, level) {
var lastShownAt = getLastOverlayShownAt();
if (!forceOpen && Date.now() - lastShownAt < CRISIS_OVERLAY_COOLDOWN_MS) {
logCrisisOverlayEvent('suppressed', level || 2);
return false;
}
logCrisisOverlayEvent(forceOpen ? 'manual-open' : 'shown', level || 2);
setLastOverlayShownAt(Date.now());
// Save current focus for restoration on dismiss
_preOverlayFocusElement = document.activeElement;
@@ -1108,7 +1053,6 @@ Sovereignty and service always.`;
// Focus the Call 988 link (always enabled) — disabled buttons cannot receive focus
if (overlayCallLink) overlayCallLink.focus();
return true;
}
// Register focus trap on document (always listening, gated by class check)
@@ -1357,12 +1301,6 @@ Sovereignty and service always.`;
});
}
if (crisisResourcesBtn) {
crisisResourcesBtn.addEventListener('click', function() {
openCrisisResources();
});
}
// ===== TEXTAREA AUTO-RESIZE =====
msgInput.addEventListener('input', function() {
this.style.height = 'auto';

View File

@@ -1,53 +0,0 @@
import pathlib
import re
import unittest
ROOT = pathlib.Path(__file__).resolve().parents[1]
INDEX_HTML = ROOT / 'index.html'
class TestCrisisOverlayRateLimit(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.html = INDEX_HTML.read_text()
def test_overlay_has_ten_minute_cooldown_constant(self):
self.assertRegex(
self.html,
r"CRISIS_OVERLAY_COOLDOWN_MS\s*=\s*10\s*\*\s*60\s*\*\s*1000",
'Expected a 10-minute crisis overlay cooldown constant.',
)
def test_show_overlay_suppresses_repeat_with_logging(self):
self.assertRegex(
self.html,
r"function\s+logCrisisOverlayEvent\s*\(",
'Expected a crisis overlay event logger.',
)
self.assertRegex(
self.html,
r"if\s*\(!forceOpen\s*&&\s*Date\.now\(\)\s*-\s*lastShownAt\s*<\s*CRISIS_OVERLAY_COOLDOWN_MS\)",
'Expected showOverlay to suppress repeated auto-displays inside the cooldown window.',
)
self.assertRegex(
self.html,
r"logCrisisOverlayEvent\('suppressed'",
'Expected suppressed overlay attempts to be logged.',
)
def test_manual_crisis_resources_button_exists_and_bypasses_cooldown(self):
self.assertIn('id="crisis-resources-btn"', self.html)
self.assertRegex(
self.html,
r"function\s+openCrisisResources\s*\(",
'Expected a manual crisis resources opener.',
)
self.assertRegex(
self.html,
r"crisisResourcesBtn\.addEventListener\('click',\s*function\(\)\s*\{\s*openCrisisResources\(\);",
'Expected the footer button to wire into openCrisisResources().',
)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,76 @@
"""Tests for the-door static site link integrity.
Validates that all internal links in HTML files point to existing files,
preventing broken navigation from deployed static servers.
"""
import os
import re
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).parent.parent
def _extract_internal_links(html_content: str) -> list[str]:
"""Extract href values that point to local paths (not http/https/mailto/#)."""
pattern = r'href="(/[^"]*|[^"]*\.html)"'
links = re.findall(pattern, html_content)
return [
link for link in links
if not link.startswith("http")
and not link.startswith("mailto:")
and not link.startswith("#")
and not link.startswith("https://")
]
def _link_to_filepath(link: str) -> Path:
"""Convert an href path to a filesystem path relative to project root."""
if link.startswith("/"):
return PROJECT_ROOT / link.lstrip("/")
return PROJECT_ROOT / link
@pytest.mark.parametrize("html_file", [
"index.html",
"about.html",
"testimony.html",
"crisis-offline.html",
])
def test_internal_links_resolve(html_file: str):
"""All internal links in HTML files should point to existing files."""
html_path = PROJECT_ROOT / html_file
if not html_path.exists():
pytest.skip(f"{html_file} not found")
content = html_path.read_text(encoding="utf-8")
links = _extract_internal_links(content)
broken = []
for link in links:
target = _link_to_filepath(link)
if not target.exists():
broken.append(f" {link} -> {target} (NOT FOUND)")
assert not broken, (
f"Broken links in {html_file}:\n" + "\n".join(broken)
)
def test_about_link_points_to_html():
"""Specific regression test: the about footer link must point to about.html, not /about."""
index_path = PROJECT_ROOT / "index.html"
content = index_path.read_text(encoding="utf-8")
# Should contain about.html link
assert 'href="/about.html"' in content, (
"Footer about link should be '/about.html', not '/about'"
)
# Should NOT contain bare /about link (which 404s on static servers)
about_links = re.findall(r'href="(/about)"', content)
assert not about_links, (
f"Found bare '/about' link(s) that will 404 on static servers: {about_links}"
)