From cd04b7a84ece25994ce204ca451aec24b7f22d3f Mon Sep 17 00:00:00 2001 From: Jay Date: Fri, 9 Oct 2026 13:46:57 +0200 Subject: [PATCH] fix(roulette): open the daily.dev post when a spared bookmark has no url Shares, freeform posts and collections come back from the Public API with url: "" (88 of 274 in a real pile), so "Spare it" opened a blank tab. Fall back to commentsPermalink, which is always set. Co-Authored-By: Claude Opus 5.5 --- src/lib/daily.ts | 3 +++ src/scripts/roulette.ts | 3 ++- tests/e2e/real-mode.spec.ts | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/lib/daily.ts b/src/lib/daily.ts index 4ed1f4d..e4fa73c 100644 --- a/src/lib/daily.ts +++ b/src/lib/daily.ts @@ -5,7 +5,10 @@ const BASE = "https://api.daily.dev/public/v1"; export type Bookmark = { id: string; title: string; + // Empty for posts that live on daily.dev itself (shares, freeform posts, + // collections); commentsPermalink is always set and opens the post there. url: string; + commentsPermalink?: string; image?: string; summary?: string; source?: { name?: string; image?: string }; diff --git a/src/scripts/roulette.ts b/src/scripts/roulette.ts index b9ea4e6..2d91806 100644 --- a/src/scripts/roulette.ts +++ b/src/scripts/roulette.ts @@ -284,7 +284,8 @@ export function initRoulette(): void { function read() { if (!state.current) return; - window.open(state.current.url, "_blank", "noopener"); + const link = state.current.url || state.current.commentsPermalink; + if (link) window.open(link, "_blank", "noopener"); removeCurrent(); state.survived++; showToast("📖 Spared! Off you go to read it.", "spare"); diff --git a/tests/e2e/real-mode.spec.ts b/tests/e2e/real-mode.spec.ts index 350c56e..7295889 100644 --- a/tests/e2e/real-mode.spec.ts +++ b/tests/e2e/real-mode.spec.ts @@ -95,6 +95,39 @@ test.describe("real mode: loading bookmarks", () => { }); }); +test.describe("real mode: sparing", () => { + test("a bookmark with no url opens its daily.dev post instead", async ({ page }) => { + // Shares, freeform posts and collections come back with url: "". + const permalink = "https://daily.dev/posts/a-shared-post-r3"; + await page.route("**/api/bookmarks", async (route) => { + if (route.request().method() !== "GET") return route.continue(); + await route.fulfill({ + json: { + items: [{ ...REAL_BOOKMARKS[0], id: "r3", url: "", commentsPermalink: permalink }], + }, + }); + }); + await page.addInitScript(() => { + const opened: string[] = []; + (window as unknown as { opened: string[] }).opened = opened; + window.open = (url) => { + opened.push(String(url)); + return null; + }; + }); + + await loadRouletteAsReal(page); + await page.locator("#spin").click(); + await expect(page.locator("#verdict")).toBeVisible({ timeout: 8000 }); + await page.getByRole("button", { name: /Spare it/ }).click(); + + await expect(page.locator("#stat-survived")).toHaveText("1"); + expect(await page.evaluate(() => (window as unknown as { opened: string[] }).opened)).toEqual([ + permalink, + ]); + }); +}); + test.describe("real mode: pulling the trigger", () => { test.beforeEach(async ({ page }) => { page.context().on("page", (p) => p.close().catch(() => {}));