From be2e12c0a5f75c0bad2178f2bb9e0c6b182f4ccf Mon Sep 17 00:00:00 2001 From: Manas Raghuwanshi Date: Mon, 10 Aug 2026 16:57:33 +0530 Subject: [PATCH] fix(onboarding): point demo mode at a host that exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The default endpoint was demo.woopcode.dev, a domain that was never registered. Every install therefore did the same thing: reported the demo unreachable and fell through to the API key prompt. The fallback worked, which is why nothing looked broken, but no user could reach the demo at all. Points at the deployed proxy instead. Verified against it rather than assumed — a session issues in ~715ms and a real turn streams back through it from Google. This string compiles into every installed copy and cannot be corrected for anyone who does not upgrade, which is what made the unregistered domain costly rather than merely wrong. If the proxy moves off Railway the fix is a CNAME on a domain that is actually owned, not another edit here. The regression test asserts the default is not the old host and is a well-formed absolute URL. It does not reach the network: what can be checked offline is that the constant is sane, and a test that pinged the proxy would fail in CI for reasons unrelated to the code. --- config/demoAccount.ts | 17 ++++++++++++++++- packages/tests/config/demoMode.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/config/demoAccount.ts b/config/demoAccount.ts index 32918f0..af97476 100644 --- a/config/demoAccount.ts +++ b/config/demoAccount.ts @@ -26,6 +26,21 @@ export const DEMO_PROVIDER = "google"; /** Marks a provider entry as demo-issued rather than user-supplied. */ export const DEMO_ENTRY_TYPE = "demo"; +/** + * Where the demo proxy lives when nothing overrides it. + * + * This string ends up compiled into every installed copy, and an installed + * copy is not something that can be corrected later — a user on an older + * version keeps asking the old host forever. It was `demo.woopcode.dev` + * before this, a domain that was never registered, so the only thing every + * install did was fail to resolve it. + * + * The escape hatch, if the proxy ever moves off Railway, is a CNAME on a + * domain that *is* owned, pointed wherever the service goes. Changing this + * constant again only helps people who upgrade. + */ +const DEFAULT_DEMO_ENDPOINT = "https://woopcode-demo-proxy-production.up.railway.app"; + /** * The proxy Woopcode's demo talks to. * @@ -36,7 +51,7 @@ export const DEMO_ENTRY_TYPE = "demo"; export function demoEndpoint( env: Record = process.env, ): string { - return (env.WOOPCODE_DEMO_URL?.trim() || "https://demo.woopcode.dev").replace( + return (env.WOOPCODE_DEMO_URL?.trim() || DEFAULT_DEMO_ENDPOINT).replace( /\/+$/, "", ); diff --git a/packages/tests/config/demoMode.test.ts b/packages/tests/config/demoMode.test.ts index 510a902..d6afb1e 100644 --- a/packages/tests/config/demoMode.test.ts +++ b/packages/tests/config/demoMode.test.ts @@ -47,6 +47,32 @@ beforeEach(async () => { ); }); +describe("the default endpoint", () => { + // The default is compiled into every installed copy and cannot be corrected + // for anyone who does not upgrade. It shipped once pointing at + // demo.woopcode.dev, a domain that was never registered, so every install + // reported the demo unreachable and fell through to the key prompt. + test("is not the unregistered host it used to be", () => { + expect(demoEndpoint({})).not.toContain("demo.woopcode.dev"); + }); + + test("is an absolute https URL with no trailing slash", () => { + const endpoint = demoEndpoint({}); + expect(endpoint).toStartWith("https://"); + expect(endpoint).not.toEndWith("/"); + }); + + test("an override wins and is normalised", () => { + expect(demoEndpoint({ WOOPCODE_DEMO_URL: "http://localhost:8787///" })).toBe( + "http://localhost:8787", + ); + }); + + test("a blank override falls back rather than producing an empty URL", () => { + expect(demoEndpoint({ WOOPCODE_DEMO_URL: " " })).toBe(demoEndpoint({})); + }); +}); + describe("a demo entry survives being stored", () => { // The regression this file exists for. normalizeConfig rebuilds every // provider entry field by field, so a field it does not name is dropped on