mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
[video-modal] Fix dead like/dislike buttons — dispatch video:reaction (#54)
The real #54 bug (the signer gate was correct but unreachable): the reactions
controller forwards the raw click event to VideoModal.handleReactionClick, which
called this.reactionsController.handleReaction(event) — a method that does NOT
exist on the video-modal reactions controller (it only binds buttons + does
optimistic UI), passing the DOM event as the "reaction". So clicking like/dislike
threw / silently did nothing and never reached the publish path.
Fix: handleReactionClick now derives +/- from the clicked button
(#modalLikeBtn / #modalDislikeBtn, or a bare "+"/"-" for programmatic callers) and
dispatches "video:reaction" — the event ModalManager already routes to
app.handleVideoReaction -> reactionController.handleReaction (signs + publishes the
kind-7, and runs the ensureEncryptionCapableSigner gate from the earlier #54 pass).
Note: #53 (channel avatar/name link) is already fixed in a20388c5
(handleCreatorNavigation now dispatches "creator:navigate"); if it still looks dead
it's a stale build — rebuild dist / redeploy the branch under test.
Tests: tests/video-modal-controllers.test.mjs (+2). Lint + build green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
757fca1059
commit
6e2894e3d8
3 files changed
+101
-3
No files matched your search
@@ -3030,10 +3030,37 @@ export class VideoModal {
|
||||
this.log("[VideoModal] More button clicked");
|
||||
}
|
||||
|
||||
handleReactionClick(reaction) {
|
||||
if (this.reactionsController) {
|
||||
this.reactionsController.handleReaction(reaction);
|
||||
handleReactionClick(event) {
|
||||
// The reactionsController binds the like/dislike buttons and forwards the raw
|
||||
// click event here. Derive WHICH button was clicked, then dispatch
|
||||
// "video:reaction" — the event ModalManager listens for
|
||||
// (→ app.handleVideoReaction → reactionController.handleReaction, which signs +
|
||||
// publishes the kind-7). The old code called this.reactionsController.handleReaction
|
||||
// (a method that doesn't exist on that controller) with the DOM event as the
|
||||
// "reaction", so like/dislike silently did nothing. Also accept a bare "+"/"-"
|
||||
// for programmatic callers.
|
||||
let reaction = "";
|
||||
if (event === "+" || event === "-") {
|
||||
reaction = event;
|
||||
} else {
|
||||
const target =
|
||||
event?.currentTarget instanceof Element
|
||||
? event.currentTarget
|
||||
: event?.target?.closest?.("#modalLikeBtn, #modalDislikeBtn") || null;
|
||||
if (target && target === this.reactionButtons?.["+"]) {
|
||||
reaction = "+";
|
||||
} else if (target && target === this.reactionButtons?.["-"]) {
|
||||
reaction = "-";
|
||||
} else if (target?.id === "modalLikeBtn") {
|
||||
reaction = "+";
|
||||
} else if (target?.id === "modalDislikeBtn") {
|
||||
reaction = "-";
|
||||
}
|
||||
}
|
||||
if (reaction !== "+" && reaction !== "-") {
|
||||
return;
|
||||
}
|
||||
this.dispatch("video:reaction", { reaction, video: this.activeVideo });
|
||||
}
|
||||
|
||||
bindVideoEvents() {
|
||||
|
||||
@@ -248,3 +248,63 @@ test("creator navigation is a no-op without an active video pubkey", async (t) =
|
||||
modal.handleCreatorNavigation();
|
||||
assert.equal(fired, false, "no pubkey → no navigation event");
|
||||
});
|
||||
|
||||
// Like/dislike in the player modal (TODO #54): clicking a reaction button must
|
||||
// dispatch "video:reaction" (→ app.handleVideoReaction → reactionController). The
|
||||
// old handleReactionClick called this.reactionsController.handleReaction (a method
|
||||
// that doesn't exist on the video-modal reactions controller) with the raw DOM
|
||||
// event, so nothing was published.
|
||||
//
|
||||
// test_integrity_note:
|
||||
// change_type: ["new_tests"]
|
||||
// scenarios:
|
||||
// - id: SCN-video-modal-reaction-dispatch
|
||||
// given: "a video modal with an active video"
|
||||
// when: "handleReactionClick runs for a like/dislike button or a bare +/-"
|
||||
// then: "it dispatches video:reaction with the derived reaction; unrelated targets do nothing"
|
||||
// observable_outcomes:
|
||||
// - "bare '+'/'-' → video:reaction with that reaction"
|
||||
// - "a click whose currentTarget is the like button → reaction '+'"
|
||||
// - "a null/unrelated target → no dispatch"
|
||||
// determinism_controls:
|
||||
// - "JSDOM modal via setupModal; synchronous dispatch"
|
||||
// anti_cheat_rationale:
|
||||
// prevents: ["hard-coded return value", "asserting the broken call path"]
|
||||
// relaxation:
|
||||
// did_relax_any_assertion: false
|
||||
|
||||
test("like/dislike dispatches video:reaction with the derived reaction", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
|
||||
modal.activeVideo = { pubkey: "a".repeat(64) };
|
||||
const events = [];
|
||||
modal.addEventListener("video:reaction", (event) => events.push(event.detail));
|
||||
|
||||
modal.handleReactionClick("+");
|
||||
modal.handleReactionClick("-");
|
||||
|
||||
assert.equal(events.length, 2, "both reactions dispatched");
|
||||
assert.equal(events[0].reaction, "+");
|
||||
assert.equal(events[1].reaction, "-");
|
||||
assert.equal(events[0].video, modal.activeVideo, "carries the active video");
|
||||
|
||||
// And via a real button click (currentTarget = the bound like button).
|
||||
if (modal.reactionButtons?.["+"]) {
|
||||
modal.handleReactionClick({ currentTarget: modal.reactionButtons["+"] });
|
||||
assert.equal(events[2]?.reaction, "+", "derived '+' from the like button element");
|
||||
}
|
||||
});
|
||||
|
||||
test("reaction click with no matching button does not dispatch", async (t) => {
|
||||
const { modal, cleanup } = await setupModal();
|
||||
t.after(cleanup);
|
||||
|
||||
let fired = false;
|
||||
modal.addEventListener("video:reaction", () => {
|
||||
fired = true;
|
||||
});
|
||||
modal.handleReactionClick({ currentTarget: null });
|
||||
modal.handleReactionClick({});
|
||||
assert.equal(fired, false, "no reaction event for an unrelated/empty target");
|
||||
});
|
||||
@@ -1659,6 +1659,17 @@ passphrase-encrypted). Items 51, 56, 57 are all facets of that.
|
||||
Tests: `tests/reaction-signer-gate.test.mjs`. **Still VERIFY on unstable** whether
|
||||
any residual dead-button behavior remains (event binding / pointer availability),
|
||||
which would be a separate wiring bug from the signer issue.
|
||||
- [x] **The actual dead-button bug — FIXED 2026-07-02.** `VideoModal.handleReactionClick`
|
||||
never derived the reaction or reached the publish path: it called
|
||||
`this.reactionsController.handleReaction(event)` — a method that does NOT exist on
|
||||
the video-modal reactions controller (which only binds buttons + does optimistic
|
||||
UI), passing the raw DOM click event as the "reaction". So the #54 signer gate was
|
||||
correct but unreachable. Now `handleReactionClick` derives `+`/`-` from the clicked
|
||||
button (`#modalLikeBtn`/`#modalDislikeBtn`) and dispatches `video:reaction` — the
|
||||
event ModalManager already routes to `app.handleVideoReaction` →
|
||||
`reactionController.handleReaction` (which signs/publishes + runs the signer gate).
|
||||
Tests: `tests/video-modal-controllers.test.mjs` (+2 — derives the reaction and
|
||||
dispatches; unrelated target is a no-op). Lint + build green.
|
||||
|
||||
### 55. Video-grid event caching — faster loads, cache-first + merge-new (PERF / UX)
|
||||
- [ ] **Channel-profile grid still loads slowly.** Even after the coalescing + relay
|
||||
|
||||
Reference in new issue
Block a user