feat(ui): collapsible sidebar section groups (bookshelf-7fc) #154
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-7fc"
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?
Summary
sidebar_section_controller.js— click label header collapses/expands the group; pressing Enter/Space also toggles (keyboard accessible)localStoragekeybookshelf:sidebar:collapsed(JSON array of group keys); no server round-trip; CSP-safe (no inline JS/styles)aria-expanded,aria-hidden,aria-controls), andtabindex=0for focusability<a>or<button>(e.g. Shelves+) does NOT toggle the section.sidebar-nav-group--collapsedhides the body; chevron rotates 90° when collapsedTest plan
make buildpassesmake testpasses (no Go changes affecting unit tests)e2e/browser/sidebar_section_test.go:+→ section does NOT collapseCloses bead bookshelf-7fc on merge.
7ec3264c5a40b4e2dfa9Security Review — PR #154 (bookshelf-7fc): Collapsible Sidebar Stimulus Controller
Scope: localStorage defensive parsing, ARIA/DOM injection, CSP compliance, "+" link click handling, html/template XSS escaping on group keys.
[MAJOR] static/js/controllers/sidebar_section_controller.js:56 —
onKeydownlacks the<a>/<button>guard present intoggle()The
toggle()method correctly bails out whenevent.targetis an<a>or<button>.onKeydown()does not. Both the Libraries and Shelves header<p>elements contain a "+"<a>child. That<a>is reachable by keyboard (Tab), and keydown events on it bubble up to the<p>. When a keyboard user presses Enter or Space to activate the "+" link,onKeydownfires on the bubbled event, callsevent.preventDefault()(suppressing the link navigation), and calls_toggle()(collapsing/expanding the section). Result: keyboard users cannot reach "New library" or "New shelf" via keyboard without inadvertently toggling the section; the Enter/Space activation of those links is silently eaten. Fix: mirror thetoggle()guard at the top ofonKeydown:[MINOR] templates/layouts/base.html:76,105,150,177,197 —
<p>toggle elements lackrole="button"The header
<p>elements havetabindex="0"and act as interactive controls, but carry no ARIA role. Screen readers will announce them as "paragraph" or "text", not as interactive buttons. Addrole="button"to each toggle<p>so assistive technology exposes the correct widget role and announcesaria-expandedstate correctly.Items confirmed clean:
readCollapsed()wrapsJSON.parsein try/catch and validatesArray.isArray(parsed), returning[]on any failure. Attacker-controlled JSON cannot crash the controller or execute code.innerHTML/eval/Function— all DOM manipulation is viaclassList.toggleandsetAttribute. No code injection vector.<script>orstyle=attributes introduced. The new<script src="...?v=...">tag follows the existing pattern;script-src 'self'permits it. No'unsafe-inline'required.toggle()checksevent.target.tagNameand returns early for<a>and<button>, so mouse clicks on "+" do not collapse the section. The e2e testsidebar_section_test.gocovers this path.data-sidebar-section-key-valueattributes are hardcoded string literals in the template ("home", "libraries", "shelves", "ingest", "system"). No Go template variable is interpolated into these attributes.bookshelf:sidebar:collapsed,indexOf(this.keyValue)performs a string equality comparison against hardcoded keys; no code is executed on array contents.writeCollapsedcatches anysetItemexception and silently ignores it; no crash path.REVIEW VERDICT: 0 blocker, 1 major, 1 minor
CODE REVIEW: NOT APPROVED
Phase 0 — DEMO Verification: PARTIAL — the bead VERIFY section specifies rod e2e tests (click collapse, reload persist, "+" does not collapse). CI is green (state: success, SHA
3de6bdb), confirming all three behaviors pass under mouse clicks. The keyboard path for "+" is not covered by a test (see Major #2 below). Proceeding to Phase 1.Phase 1 — Spec Compliance: All five requirements (toggle, localStorage, Stimulus controller, CSS, keyboard/ARIA) are addressed. The "+" affordance does NOT trigger collapse on mouse click (req 1). localStorage with invalid-JSON resilience is handled (readCollapsed try/catch, lines 16–24). Key
bookshelf:sidebar:collapsedhas abookshelf:prefix; collision risk is low. CSP-safe: no inline JS, no inline styles, toggling is class-based. Stimulus naming is correct:sidebar_section_controller.js→data-controller="sidebar-section". Phase 1 passes.Phase 2 — Code Quality:
[MAJOR] static/js/controllers/sidebar_section_controller.js:56 —
onKeydownmissing the<a>/<button>target guard thattoggle()hasThe
toggle()method at line 46–53 correctly returns early ifevent.targetis an<a>or<button>. TheonKeydown()method at line 56–61 calls_toggle()unconditionally. Thekeydownaction is bound to the<p>header element which receives bubbled keydown events from all its children. When a keyboard user tabs to the "+" link inside the LIBRARIES or SHELVES header and presses Enter, the browser activates the link (navigation) AND the bubbled keydown event triggersonKeydown()→_toggle()— flipping the localStorage state to collapsed. After the navigation returns to the page, the section appears collapsed even though the user was only activating the "+". Fix: add the same guard toonKeydown:[MAJOR] templates/layouts/base.html:73,102,147,174,194 —
<p>interactive toggle elements missingrole="button"Each section header is a
<p>element withtabindex="0",aria-expanded,aria-controls, and keyboard/click actions. The implicit ARIA role of<p>isparagraph. Screen readers (NVDA, VoiceOver, JAWS) will not announce these elements as interactive and may not surface thearia-expandedstate, becausearia-expandedis only meaningful on roles that support it (button, combobox, listbox, etc.). Per WCAG 4.1.2 (Name, Role, Value), interactive custom controls must have an appropriate role. Fix: addrole="button"to each<p>that carriestabindex=0+aria-expanded. Alternatively, consider replacing the<p>with an actual<button>element (which natively conveys interactivity, supports Enter/Space, and needs notabindex).[MINOR] e2e/browser/sidebar_section_test.go — no keyboard-activation test for the "+" link
The "+" non-collapse test at line 75–106 uses
MustClick()(mouse event). TheonKeydownbug (#1 above) is not exercised. A test that focuses the "+" link and dispatchesnew KeyboardEvent('keydown', {key:'Enter', bubbles:true})on it would have caught this. Worth adding in the fix commit.REVIEW VERDICT: 0 blocker, 2 major, 1 minor
caec83c4ad5bd4d0b40f5bd4d0b40f941aef475b