From 95b92a07f1feba340bbb3bbd556b47935d9e6d8e Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Thu, 17 Sep 2026 10:52:59 -0700 Subject: [PATCH] Show ANAME for apex domains --- e2e/USER_STORIES.md | 5 ++-- e2e/specs/admin.spec.ts | 16 +++++++++--- src/app/components/PrimaryNav.svelte | 6 +++++ src/app/components/PrimaryNavSpaces.svelte | 8 ++---- .../components/hosting/RelayDetailCard.svelte | 25 +++++++++++++------ src/app/hosting.ts | 2 +- src/lib/components/Divider.svelte | 7 +++--- 7 files changed, 45 insertions(+), 24 deletions(-) diff --git a/e2e/USER_STORIES.md b/e2e/USER_STORIES.md index 7c1a537f..1f5d758f 100644 --- a/e2e/USER_STORIES.md +++ b/e2e/USER_STORIES.md @@ -1519,8 +1519,9 @@ space carries my branding. Acceptance: - Saving a domain under "Manage" shows it on the relay card with a "Pending" - badge and the CNAME record to configure, whose copy button puts the target on - the clipboard. + badge and the DNS record to configure, whose copy button puts the target on + the clipboard. A subdomain gets a CNAME; a bare domain, which DNS won't let + take one, gets an ALIAS. - "Verify DNS record" flips the badge to "Verified" once the backend reports it. - The relay's displayed address then switches to the custom domain. diff --git a/e2e/specs/admin.spec.ts b/e2e/specs/admin.spec.ts index 0cf90116..a5a86463 100644 --- a/e2e/specs/admin.spec.ts +++ b/e2e/specs/admin.spec.ts @@ -931,15 +931,23 @@ test("US-101 point a custom domain at a hosted relay", async ({seed, as}) => { const domainForm = dialog(page, "Custom domain") - await domainForm.getByPlaceholder("relay.example.com").fill("other.test") + await domainForm.getByPlaceholder("relay.example.com").fill("relay.other.test") await domainForm.getByRole("button", {name: "Save", exact: true}).click() await expect(page.getByRole("alert")).toContainText("Custom domain saved.") - await expect(page.getByText("other.test", {exact: true})).toBeVisible() + await expect(page.getByText("relay.other.test", {exact: true})).toBeVisible() await expect(page.getByText("Pending", {exact: true})).toBeVisible() - await expect(page.getByText("other.test CNAME space.test")).toBeVisible() + await expect(page.getByText("relay.other.test CNAME space.test")).toBeVisible() - await page.locator('[data-tip="Copy CNAME target"]').click() + // A bare domain can't take a CNAME, so it's shown as an ALIAS at the same target + await page.getByRole("button", {name: "Manage"}).click() + await domainForm.getByPlaceholder("relay.example.com").fill("other.test") + await domainForm.getByRole("button", {name: "Save", exact: true}).click() + + await expect(page.getByText("other.test", {exact: true})).toBeVisible() + await expect(page.getByText("other.test ALIAS space.test")).toBeVisible() + + await page.locator('[data-tip="Copy ALIAS target"]').click() await expect(page.getByRole("alert")).toContainText("Copied to clipboard!") expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("space.test") diff --git a/src/app/components/PrimaryNav.svelte b/src/app/components/PrimaryNav.svelte index ad966ca3..54473070 100644 --- a/src/app/components/PrimaryNav.svelte +++ b/src/app/components/PrimaryNav.svelte @@ -2,6 +2,7 @@ import type {Snippet} from "svelte" import cx from "classnames" import {Profiles} from "@welshman/app" + import Compass from "@assets/icons/compass.svg?dataurl" import Letter from "@assets/icons/letter.svg?dataurl" import Magnifier from "@assets/icons/magnifier.svg?dataurl" import UserRounded from "@assets/icons/user-rounded.svg?dataurl" @@ -67,6 +68,11 @@ {/if} + {#if PLATFORM_RELAYS.length === 0} + + + + {/if} import cx from "classnames" - import Widget from "@assets/icons/widget-4.svg?dataurl" import ImageIcon from "@lib/components/ImageIcon.svelte" import Divider from "@lib/components/Divider.svelte" import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte" @@ -46,11 +45,11 @@ - +
- - - {/each} diff --git a/src/app/components/hosting/RelayDetailCard.svelte b/src/app/components/hosting/RelayDetailCard.svelte index 60f71700..df213fa7 100644 --- a/src/app/components/hosting/RelayDetailCard.svelte +++ b/src/app/components/hosting/RelayDetailCard.svelte @@ -70,7 +70,11 @@ const host = $derived(relayHost(current)) const relayLabel = $derived(current.info_name || current.subdomain) const domainVerified = $derived(flagToBool(current.custom_domain_verified, false)) - const cnameTarget = $derived(canonicalRelayHost(current)) + const recordTarget = $derived(canonicalRelayHost(current)) + // DNS forbids a CNAME at a zone apex, so those domains point at the same + // target using their provider's ALIAS/ANAME record instead. + const isApex = $derived(current.custom_domain.split(".").length === 2) + const recordType = $derived(isApex ? "ALIAS" : "CNAME") const isPaidPlan = $derived(current.plan_id !== "free") // Adding, removing or verifying a custom domain moves the relay's host, so @@ -156,7 +160,7 @@ showMenu = false } - const copyCname = () => clip(cnameTarget) + const copyTarget = () => clip(recordTarget) // Verification runs in a backend poller; reload to pick up the result. const verify = async () => { @@ -346,18 +350,23 @@
- {current.custom_domain} CNAME {cnameTarget} + {current.custom_domain} + {recordType} + {recordTarget}
-

- For apex domains (e.g. example.com), use an ALIAS or ANAME record instead. -

+ {#if isApex} +

+ A bare domain can't use a CNAME. Your provider may call this record ANAME, or offer + CNAME flattening instead. +

+ {/if}