diff --git a/src/layouts/global.astro b/src/layouts/global.astro index d0a47f5..6900922 100644 --- a/src/layouts/global.astro +++ b/src/layouts/global.astro @@ -10,18 +10,28 @@ interface Props { title: string; description: string; + // Show the signup form. Blog posts get it automatically; other pages opt in, + // since it has no business interrupting the demo and player pages. + subscribe?: boolean; + frontmatter?: { title: string; date: string; description: string; cover?: string; + subscribe?: boolean; }; } let { title, frontmatter, description } = Astro.props; +const { subscribe } = Astro.props; if (frontmatter?.title) title = frontmatter.title; if (frontmatter?.description) description = frontmatter.description; +// `date` is what makes a page a blog post. An explicit flag, from either a prop +// (.astro pages) or frontmatter (.mdx pages), wins over that default. +const showSubscribe = subscribe ?? frontmatter?.subscribe ?? Boolean(frontmatter?.date); + const siteUrl = Astro.site?.toString().replace(/\/$/, "") ?? "https://moq.dev"; const pageUrl = new URL(Astro.url.pathname, siteUrl).toString(); const fullTitle = title ? `${title} - Media over QUIC` : "Media over QUIC"; @@ -121,7 +131,7 @@ const proUrl = import.meta.env.MODE === "staging" ? "https://moq.wtf" : "https:/ ) } - {frontmatter?.date && } + {showSubscribe && } diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro index 8fb0b71..285cb93 100644 --- a/src/pages/blog/index.astro +++ b/src/pages/blog/index.astro @@ -20,7 +20,7 @@ posts.sort((a, b) => { }); --- - +

Blog Posts

diff --git a/worker/index.ts b/worker/index.ts index cc81084..230bde4 100644 --- a/worker/index.ts +++ b/worker/index.ts @@ -48,7 +48,9 @@ async function handleSubscribe(request: Request, env: Env): Promise { body: JSON.stringify({ email, unsubscribed: false, - segments: [env.RESEND_SEGMENT_ID], + // Objects, not bare ids. A string here is a 422 that used to be reported + // to the visitor as success, so every signup was dropped on the floor. + segments: [{ id: env.RESEND_SEGMENT_ID }], }), }); } catch (err) { @@ -56,14 +58,13 @@ async function handleSubscribe(request: Request, env: Env): Promise { return json({ error: "subscribe failed" }, 502); } - // Treat any non-5xx as success so we don't leak whether an address is - // already on the list (Resend returns 4xx for duplicates). Log 4xx for - // debugging since a misconfigured segment ID would silently break sends. + // Resend upserts contacts: re-subscribing an existing address returns 201 with + // the same contact id, so there is no duplicate to hide and no reason to + // swallow a 4xx. Anything not ok is a real failure (malformed body, unknown + // segment), and calling those success is what silently discarded every signup. // Only log status + request id, not the body (which may contain the email). if (!res.ok) { console.error(`Resend POST /contacts → ${res.status} (request-id: ${res.headers.get("x-request-id") ?? "n/a"})`); - } - if (res.status >= 500) { return json({ error: "subscribe failed" }, 502); }