Affected files: frontend/src/directives/ripple.js:14, frontend/src/directives/ripple.test.js:79
Description: spawnRipple's ripple diameter is calculated as Math.max(rect.width, rect.height), the element's longest edge. This under-covers a rectangular (non-square) host element: the maximum distance a ripple centred on a click point may need to travel to reach the farthest corner is the element's diagonal, not its longest edge.
Failure scenario: On a wide element (e.g. a 200x40px row once v-ripple is wired to list items in a later Material Design stage), a click near one edge spawns a ripple that visually clips and fades out before reaching the opposite side, rather than sweeping the full element per Material Design's ripple spec. Low impact today since nothing currently uses v-ripple (it's registered globally but unattached, per PR #281's Stage 1 scope) and the only near-term consumers (.btn, .icon-btn) are close to square, but this will matter once Stage 2+ wires the directive to list rows and other non-square elements.
Recommended fix: Use the diagonal, doubled, so the ripple can reach any corner from any click point within the element: const size = Math.sqrt(rect.width ** 2 + rect.height ** 2) * 2. Update the geometry assertions in ripple.test.js accordingly (for the existing 40x40 fixture, this changes the expected width/height from 40px to approximately 113.137px; left/top stay 0px since the fixture's click point is centred).
Found via an adversarial code review (Gemini 3.1 Pro) of PR #281, independently verified.
Affected files:
frontend/src/directives/ripple.js:14,frontend/src/directives/ripple.test.js:79Description:
spawnRipple's ripple diameter is calculated asMath.max(rect.width, rect.height), the element's longest edge. This under-covers a rectangular (non-square) host element: the maximum distance a ripple centred on a click point may need to travel to reach the farthest corner is the element's diagonal, not its longest edge.Failure scenario: On a wide element (e.g. a 200x40px row once
v-rippleis wired to list items in a later Material Design stage), a click near one edge spawns a ripple that visually clips and fades out before reaching the opposite side, rather than sweeping the full element per Material Design's ripple spec. Low impact today since nothing currently usesv-ripple(it's registered globally but unattached, per PR #281's Stage 1 scope) and the only near-term consumers (.btn,.icon-btn) are close to square, but this will matter once Stage 2+ wires the directive to list rows and other non-square elements.Recommended fix: Use the diagonal, doubled, so the ripple can reach any corner from any click point within the element:
const size = Math.sqrt(rect.width ** 2 + rect.height ** 2) * 2. Update the geometry assertions inripple.test.jsaccordingly (for the existing 40x40 fixture, this changes the expectedwidth/heightfrom40pxto approximately113.137px;left/topstay0pxsince the fixture's click point is centred).Found via an adversarial code review (Gemini 3.1 Pro) of PR #281, independently verified.