script-src now allow-lists the guard by sha256 of its rendered bytes
(middleware.ScriptHashThemeFlashGuard) instead of 'unsafe-inline' — the
guard is the app's only inline <script>. security_headers_test.go renders
templates.ThemeFlashGuard() and fails with the replacement hash if the
script text ever drifts.
Executing anti-XSS strict CSP surfaced three latent violations the
eval-removal sweep couldn't see (hash mode is stricter than eval mode,
because inline event handler ATTRIBUTES now count against script-src):
1. admin_archived / admin_library: 15 native onclick=/onsubmit= handlers
(modal open/close, folder-browser picker, purge button) converted to
Alpine @click/@submit component methods — openCreateLibraryModal,
hideModal, pickBrowsePath added to the admin component;
purgeArchivedItems promoted from its window global into the component
and the (window as any) exposure removed.
2. dashboard/docs: onclick handlers converted — dashboard's
event.stopPropagation() → @click.stop; docs' sidebar/search closers →
closeDocsSidebar/closeSearchResults component methods.
3. 8 onerror= image-fallback attributes (cover placeholders app-wide +
audiobook cover hide) replaced by data-fallback attributes handled by
one delegated capture-phase listener in web/src/image_fallback.ts —
works for htmx-swapped content without re-binding.
Verified: theme guard applies a saved theme on public pages under the
hash CSP; 14-page authenticated sweep clean; admin pages clean under the
promoted admin; admin-library modal open/close bindings exercised;
readers render error-free; go build/vet/middleware-test, tsc, vp
check/test (146), build:ts all green.