feat(sse): live BookDrop queue-count badge via SSE (bookshelf-t3z2w.6) #1244
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-t3z2w.6"
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
KindBookdropQueueCountSSE event +BookdropBroadcastrouting mode that only reaches subscribers withcanAccessBookdroppermission (resolved viausers.GetPermissionsinsseExtractClaims, fail-closed on DB error — no info-leak to non-bookdrop users)hub.SubscribegainscanAccessBookdrop boolparam; newmatches()branch returns true only for bookdrop-access subscribers onBookdropBroadcasteventsNotifyBookdropQueueCounthelper emits on accept, reject, bulk-accept, bulk-reject, and watcher ingest; worker passesnil(no SSE hub in worker process — accepted limitation per bead spec, badge corrects on next page load)callNotifyhelper keepsAcceptProposalHandlerfunlen ≤ 60bookdrop_queue_push_controller.jsStimulus controller mirrorslibrary_counts_push_controller.js, mounted on the BookDrop<li>inside{{if .CurrentUser.CanAccessBookdrop}}Test plan
make test— all Go unit tests passnpm test— 4348 JS tests pass (11 new forbookdrop_queue_push_controller)go build ./...— compiles cleanmake lint— clean (no new funlen exclusions)Closes bead bookshelf-t3z2w.6 on merge.
Add KindBookdropQueueCount SSE event + BookdropBroadcast routing mode so the sidebar BookDrop badge updates in real time when the pending-review queue changes (accept, reject, watcher ingest), mirroring t3z2w.4 for library counts. Key points: - BookdropBroadcast routing: only reaches subscribers with canAccessBookdrop (resolved via users.GetPermissions in sseExtractClaims, fail-closed on error) - hub.Subscribe gains canAccessBookdrop bool param; matches() returns true only for bookdrop-access subscribers on BookdropBroadcast events - NotifyBookdropQueueCount helper emits from activities/handlers, never workflow body (replay safety); worker passes nil — accepted limitation per bead spec - callNotify helper keeps handler funlen ≤ 60 - bookdrop_queue_push Stimulus controller mirrors library_counts_push, mounted on BookDrop <li> inside {{if .CurrentUser.CanAccessBookdrop}} - 11 new Vitest tests + Go tests for notify call/no-call on success/failure Closes bead bookshelf-t3z2w.6 on merge. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Security re-review — PR #1244 (bd-bookshelf-t3z2w.6)
Live SSE BookDrop queue-count badge via shared SSE singleton. Adversarial focus: info-leak / server-side gating / cross-subscriber leakage / payload sensitivity / emit-site.
Findings
No blockers, majors, or minors.
Verification notes
(1) Info-leak — server-side gating is fail-closed and authoritative.
Event.BookdropBroadcastroutes viaHub.matches()ininternal/sse/hub.go:143—if e.BookdropBroadcast { return sub.canAccessBookdrop }. A subscriber only RECEIVES the event if its storedcanAccessBookdropis true. Gating is SERVER-side on the delivery path, not client filtering.canAccessBookdropis resolved once at subscribe time ininternal/app/app.gosseExtractClaimsviausers.GetPermissions(admin short-circuit → all perms; non-admin → DB rowPermissionAccessBookdrop). On a permission-resolution error it returnscanAccessBookdrop=false(fail-closed) while still allowing the connection — the user simply never receives bookdrop events.internal/sse/event.go): an event with no flag reaches nobody;NotifyBookdropQueueCountsets exactlyBookdropBroadcast=true.(2) Shared client singleton does not leak across subscriber kinds.
sse_singleton.jsmultiplexes viaes.addEventListener(eventName, handler)— each handler is bound to its own NAMED event (library.countsvsbookdrop.queue_count), so the library controller never receives bookdrop frames and vice versa. Even if a non-bookdrop page manually subscribed tobookdrop.queue_count, the server never emits that frame onto its EventSource (see (1)), so nothing is delivered.bookdrop-queue-pushcontroller mounts only inside{{if .CurrentUser.CanAccessBookdrop}}inbase.html:345, so unauthorized pages never even subscribe.(3) Payload is count-only — no sensitive data.
BookdropQueueCountPayload{Count int64}(internal/sse/event.go). No paths, filenames, user IDs, or PII. Client validatestypeof count === numberand renders viatextContent(no XSS).(4) Emit sites are handler/watcher, not a workflow body.
AcceptProposalHandler/RejectProposalHandler/BulkAcceptHandler/BulkRejectHandler(internal/bookdrop/review_handler.go) and from the bookdrop watcher goroutine inapp.gostartBookdropGoroutines. All are plain Go handler/goroutine code — nogowfcommand emission, so no workflow replay/version-gate concern. Errors inNotifyBookdropQueueCountare logged and swallowed so notify never blocks the primary accept/reject/ingest.Global
CountBookdropPendingReview(unscoped) is correct: bookdrop is a global permission-gated surface (matches the existingNavCounts.BookdropQueuesidebar render), and BookdropBroadcast confines the event to bookdrop-capable users.REVIEW VERDICT: 0 blocker, 0 major, 0 minor
Code Review: #1230 — BookDrop Import Metadata Modal → Inline
Phase 1: Spec Compliance
Modal Removal: Complete ✓
<div class="modal-overlay" id="import-metadata-modal">) fully removed from templateopen(),close(),onOverlayClick()removed from bookdrop-import-metadata controllerselectedCount,fetchBtn,applyBtn,resultsArea,overlay#open→#fetch, ID:#import-metadata-open-btn→#import-metadata-fetch-btnapplyUrlValuedata attribute removed; apply endpoint no longer referencedInline Expand/Compare Routing: Correct ✓
bfe-panel--no-fetchedsetFetchedData(candidate)public method (bookdrop-file-editor) called by import-metadata on fetch result_onFetchDone(): querySelector +getControllerForElementAndIdentifier()finds row by proposal_id, callssetFetchedData(c)if (c.error) return;CSS + Styling: CSP-Compliant ✓
style=attributes.bfe-panel--no-fetchedmodifier hides right column + arrows + copy-bar; grid collapses to 1 column<dl>grid layout with proper gap/column rules.btn,.form-control,.bfe-*conventionsPhase 2: Code Quality
Tests: 100% Coverage (Both Controllers) ✓
bookdrop_file_editor_controller.test.js: 60+ specs
setFetchedData()removesbfe-panel--no-fetched, opens panel, shows badge/notice<dl>when candidate.comic exists, hidden for ebooksfetchedCoverImg,fetchedCoverPlaceholder,comicDetailsSpacer,comicDetailsArrow,comicDetailsSectionbookdrop_import_metadata_controller.test.js: 40+ specs
setFetchedData()on matching file-editor controller (via spied getControllerForElementAndIdentifier)package *_test) ✓E2E Browser Test: Cross-Controller Wiring ✓
Error Paths: Comprehensive ✓
Logic: No Race Conditions ✓
No Security Issues ✓
Conventions Followed ✓
REVIEW VERDICT: 0 blocker, 0 major, 0 minor
This is a well-executed UX refactor: modal → inline expandable rows. The routing logic is clean, error handling is thorough, test coverage is comprehensive (60+ JS specs + updated e2e), and the code follows all project conventions. No correctness issues found.
Re-Review: SSE Singleton Refactor (bd-bookshelf-t3z2w.6)
Reviewed the full diff focusing on the new SSE singleton architecture and its integration with bookdrop queue-count live updates.
Findings
CHECK 1: SSE Singleton Multiplexing (CORRECT)
CHECK 2: Library Counts Integration (PASSING)
CHECK 3: Bookdrop Queue Count - Event Routing GATED
CHECK 4: Replay Safety (NOT workflow-scoped)
CHECK 5: 100% JS Coverage
CHECK 6: Singleton Quality (NOT fragile)
CHECK 7: Bookdrop Info Leak PREVENTED
CHECK 8: Go Server-Side Integration
Verdict
The refactor is architecturally sound and well-tested. The SSE singleton correctly multiplexes multiple event types over one connection, eliminating browser-test flakes. Library-counts behavior is preserved. Bookdrop queue-count is properly gated to authorized users. All three JS controllers have comprehensive test coverage.
No findings.
REVIEW VERDICT: 0 blocker, 0 major, 0 minor
64aea36ef94c9da68f72