diff --git a/public/devices/README.md b/public/devices/README.md index c1c452d..9e81eed 100644 --- a/public/devices/README.md +++ b/public/devices/README.md @@ -187,7 +187,11 @@ package: - `logitech-mx-anywhere-3.png` — Logitech MX Anywhere 3 top-view render - `logitech-mx-ergo-s.png` — Logitech MX Ergo S top-view render - `razer-deathadder-v2.png` — Razer DeathAdder V2 (V2 / V2 Pro / Essential share the shell) -- `razer-deathadder-v3.png` — Razer DeathAdder V3 render +- `razer-deathadder-v3.png` — Razer DeathAdder V3 render. The DeathAdder V3 Pro + shares the shell and resolves to this file too (a dedicated cable-free V3 Pro + render would be an improvement). **Not in the bucket as of September 2026**: + `img.openmouse.app/razer-deathadder-v3.png` returns 404, so both models show + the placeholder (and the showcase entry 404s) until a maintainer uploads it. - `razer-deathadder-v4-pro.png` — Razer DeathAdder V4 Pro (Carbon Fiber SKU shares the shell) - `razer-viper-v3-hyperspeed.png` — Razer Viper V3 HyperSpeed render - `razer-viper-v4-pro.png` — Razer Viper V4 Pro render diff --git a/src/ui/device-images.test.ts b/src/ui/device-images.test.ts index 3504c20..101ff58 100644 --- a/src/ui/device-images.test.ts +++ b/src/ui/device-images.test.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { deviceImage } from "./device-images.ts"; +import { deviceImage, isUnknownDevice } from "./device-images.ts"; const CDN = "https://img.openmouse.app/"; @@ -141,13 +141,16 @@ test("DeathAdder V2 family shares the V2 render; V4 Pro gets its own", () => { assert.equal(deviceImage(null, "DeathAdder Essential"), CDN + "razer-deathadder-v2.png"); assert.equal(deviceImage(null, "DeathAdder V4 Pro"), CDN + "razer-deathadder-v4-pro.png"); assert.equal(deviceImage(null, "DeathAdder V4 Pro Carbon Fiber Edition"), CDN + "razer-deathadder-v4-pro.png"); - // Test-needed V3 Pro and V2 X HyperSpeed must NOT pick up V3/V2 artwork. - assert.equal(deviceImage(null, "DeathAdder V3 Pro"), CDN + "unknown-device.png"); + // Test-needed V2 X HyperSpeed must NOT pick up V2 artwork. assert.equal(deviceImage(null, "DeathAdder V2 X HyperSpeed"), CDN + "unknown-device.png"); }); -test("DeathAdder V3 wired resolves by name", () => { +test("DeathAdder V3 and V3 Pro share the V3 render", () => { assert.equal(deviceImage(null, "DeathAdder V3"), CDN + "razer-deathadder-v3.png"); + // The name the Razer driver reports for the verified 0x00b7 receiver. + assert.equal(deviceImage(null, "Razer DeathAdder V3 Pro"), CDN + "razer-deathadder-v3.png"); + assert.equal(deviceImage(null, "DeathAdder V3 Pro (Wired)"), CDN + "razer-deathadder-v3.png"); + assert.equal(isUnknownDevice(null, "DeathAdder V3 Pro"), false); }); test("Viper family resolves each generation to its own render", () => { diff --git a/src/ui/device-images.ts b/src/ui/device-images.ts index c8910ee..25436ca 100644 --- a/src/ui/device-images.ts +++ b/src/ui/device-images.ts @@ -79,7 +79,11 @@ function resolveDeviceImageFilename(_device: HIDDevice | null | undefined, displ if (/\bmx\s*anywhere\s*3\b/i.test(displayName)) return "logitech-mx-anywhere-3.png"; if (/\bmx\s*ergo\b/i.test(displayName)) return "logitech-mx-ergo-s.png"; if (/\bdeathadder\s*v4\b/i.test(displayName)) return "razer-deathadder-v4-pro.png"; - if (/\bdeathadder\s*v3\b(?!\s*pro\b)/i.test(displayName)) return "razer-deathadder-v3.png"; + // V3 and V3 Pro are one shell (the Pro drops the cable), so the Pro shares + // the V3 render the same way the V2 family shares one below. The Pro used + // to be excluded here while it was still test-needed; it has since been + // verified on hardware (mouse-protocol `0x00b7`). + if (/\bdeathadder\s*v3\b/i.test(displayName)) return "razer-deathadder-v3.png"; if (/\bdeathadder\s*v2\b(?!\s*x\s*hyperspeed\b)/i.test(displayName)) return "razer-deathadder-v2.png"; if (/\bdeathadder\s*essential\b/i.test(displayName)) return "razer-deathadder-v2.png"; if (/\bviper\s*v3\s*hyperspeed\b/i.test(displayName)) return "razer-viper-v3-hyperspeed.png";