The hash-allow-list CSP refuses inline onclick=/onerror= attributes, so
three TS islands shipped dead controls:
- stats.ts rendered Most-Read/Longest covers with onerror= — every
/stats load logged violations (5+ on the dev data) and the broken
cover fallback never ran. Now: capture-phase error listeners on the
three list containers (they persist across innerHTML swaps; error
does not bubble but capture still reaches them).
- custom-section-builder.ts picker rows and selection chips used
onclick= against window globals. Rows now carry data-book-id/title/
author and chips data-remove-book; initCustomSectionBuilder wires one
delegated click listener per container. window.addBookToSelection/
removeBookFromSelection exports removed — no remaining references.
- linking.ts: the unlinked-books 'Link' button called
window.showMatchModal, which never existed anywhere (doubly dead:
missing global AND CSP-blocked); suggestion rows and Cancel used
onclick= too. All now data-attribute driven through one document-level
delegated listener; row Link opens the suggestions flow via
getSuggestions(progressId), which fetches matches and opens the modal.
Verified: tsc --noEmit, vp check, vitest (198), manual modal flows.