From 8b54efef6191a64b1d8d8ebfd492909c1e3d7a0a Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Thu, 26 Mar 2026 20:16:12 -0400 Subject: [PATCH] audit pass 9: XSS fix, focus management, ARIA labels, and :focus-visible gaps JS: - Lightbox: remove keydown listener on close (memory leak fix) - Lightbox: restore focus to trigger on close - Right drawer: fix aria-hidden="false" anti-pattern to removeAttribute - Markdown renderer: block javascript:/data: protocol URIs in link and image replacements to prevent XSS - Sidebar submenus: add aria-expanded tracking on toggle; hide decorative chevron from screen readers; initialize aria-expanded on mount CSS: - Add :focus-visible to .lt-sidebar-toggle (interactive button) - Add :focus-visible to .lt-dropzone (focusable container) - Fix .lt-stat-card:focus-visible outline-offset to -2px (clip-path clips it) - Add light theme override for .lt-nav-drawer-link:focus-visible - Adjust .lt-split-divider:focus-visible outline-offset to 3px HTML: - Range input: update aria-valuenow dynamically on input event - Combobox label: add for="demo-combobox-input" association - Typeahead label: add for="demo-typeahead-input" association - Dropzone file input: add aria-label - Notification items: add descriptive aria-label to all 4 items; add aria-hidden="true" to decorative dot spans - Mark all read button: add type="button" to prevent accidental form submit Co-Authored-By: Claude Sonnet 4.6 --- base.css | 7 +++++-- base.html | 26 +++++++++++++------------- base.js | 26 ++++++++++++++++++++------ 3 files changed, 38 insertions(+), 21 deletions(-) diff --git a/base.css b/base.css index 8596f8e..d86aa71 100644 --- a/base.css +++ b/base.css @@ -1425,6 +1425,7 @@ select option:checked { transition: var(--transition-fast); } .lt-sidebar-toggle:hover { color: var(--accent-cyan); text-shadow: var(--glow-cyan); } +.lt-sidebar-toggle:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; } .lt-sidebar-body { padding: var(--space-md); } @@ -1502,7 +1503,7 @@ select option:checked { border-color: var(--accent-cyan-border); box-shadow: var(--box-glow-cyan); } -.lt-stat-card:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; } +.lt-stat-card:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; } .lt-stat-card:hover::before, .lt-stat-card.active::before { height: 100%; } @@ -2774,6 +2775,7 @@ input[type="range"].lt-range::-moz-range-thumb { position: relative; clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px)); } +.lt-dropzone:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; } .lt-dropzone:hover, .lt-dropzone.drag-over { border-color: var(--accent-cyan); @@ -3619,6 +3621,7 @@ html[data-theme="light"] .lt-nav-link.active { color: var(--accent-orange html[data-theme="light"] .lt-nav-drawer-link { color: var(--text-secondary); } html[data-theme="light"] .lt-nav-drawer-link:hover { background: var(--accent-cyan-dim); color: var(--accent-cyan); } html[data-theme="light"] .lt-nav-drawer-link.active { color: var(--accent-orange); background: var(--accent-orange-dim); } +html[data-theme="light"] .lt-nav-drawer-link:focus-visible { outline: none; color: var(--accent-cyan); background: var(--accent-cyan-dim); box-shadow: inset 3px 0 0 var(--accent-cyan); } html[data-theme="light"] .lt-sidebar-nav-link { color: var(--text-secondary); } html[data-theme="light"] .lt-sidebar-nav-link:hover { background: var(--accent-cyan-dim); } html[data-theme="light"] .lt-sidebar-nav-link.active { background: var(--accent-orange-dim); color: var(--accent-orange); } @@ -4522,7 +4525,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas } .lt-split-divider:hover, .lt-split-divider.is-dragging { background: var(--accent-cyan); } -.lt-split-divider:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; } +.lt-split-divider:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 3px; } .lt-split--vertical .lt-split-divider::before { top: -6px; bottom: -6px; left: 0; right: 0; cursor: row-resize; } .lt-split--vertical .lt-split-divider { cursor: row-resize; } /* On mobile, stack vertically and hide divider */ diff --git a/base.html b/base.html index b3d2062..4a95eb8 100644 --- a/base.html +++ b/base.html @@ -126,32 +126,32 @@