From 496448aa2f31e0209787f2d0f8e9735e9b9a56d3 Mon Sep 17 00:00:00 2001
From: JSONbored <49853598+JSONbored@users.noreply.github.com>
Date: Sun, 12 Jul 2026 23:54:48 -0700
Subject: [PATCH] feat(loopover): flip site-identity domain to loopover.ai
Advances #4765. Flips PUBLIC_SITE_ORIGIN (+ CORS, OAuth returnTo fallback,
UI-facing links/docs/sitemap, ORB broker/collector defaults, OpenAPI server
URL) from gittensory.aethereal.dev / gittensory-api.aethereal.dev to
loopover.ai / api.loopover.ai as the new default identity.
PUBLIC_API_ORIGIN is intentionally left unchanged: flipping it would send a
new redirect_uri during GitHub OAuth, which requires the GitHub App's
registered callback URL to be updated first. That flip lands separately
once the callback URL is confirmed.
The old gittensory.aethereal.dev domain stays live (CORS allowlist,
Worker route, and PUBLIC_SITE_ORIGIN_ALIASES) as a backward-compatible
alias, not a hard cutover.
---
.github/workflows/ci.yml | 4 +-
.github/workflows/release-selfhost.yml | 2 +-
.github/workflows/ui-deploy.yml | 2 +-
.github/workflows/ui-preview-deploy.yml | 2 +-
.github/workflows/ui-preview.yml | 2 +-
apps/gittensory-ui/public/openapi.json | 2 +-
apps/gittensory-ui/public/robots.txt | 2 +-
apps/gittensory-ui/public/sitemap.xml | 70 +++++++++----------
.../src/components/site/site-footer.tsx | 4 +-
apps/gittensory-ui/src/lib/analytics-proxy.ts | 2 +-
apps/gittensory-ui/src/lib/api/origin.ts | 2 +-
apps/gittensory-ui/src/routes/__root.tsx | 4 +-
apps/gittensory-ui/src/routes/agents.tsx | 2 +-
.../src/routes/docs.mcp-clients.tsx | 2 +-
.../routes/docs.self-hosting-github-app.tsx | 2 +-
.../src/routes/docs.troubleshooting.tsx | 4 +-
apps/gittensory-ui/src/routes/index.tsx | 2 +-
apps/gittensory-ui/wrangler.jsonc | 2 +-
scripts/smoke-production.mjs | 6 +-
scripts/smoke-ui-browser.mjs | 2 +-
scripts/write-ui-openapi.ts | 2 +-
src/api/routes.ts | 3 +-
src/auth/github-oauth.ts | 4 +-
src/env.d.ts | 2 +-
src/github/footer.ts | 2 +-
src/github/repo-doc-pr.ts | 2 +-
src/orb/broker-client.ts | 2 +-
src/review/repo-doc-render.ts | 2 +-
src/review/visual/capture.ts | 2 +-
src/selfhost/orb-collector.ts | 2 +-
src/signals/engine.ts | 2 +-
test/helpers/d1.ts | 2 +-
test/integration/orb-oauth.test.ts | 4 +-
test/integration/routes-errors.test.ts | 4 +-
test/unit/auth.test.ts | 8 +--
test/unit/github-app.test.ts | 2 +-
test/unit/github-commands.test.ts | 4 +-
test/unit/orb-broker-client.test.ts | 6 +-
test/unit/queue-4.test.ts | 2 +-
worker-configuration.d.ts | 6 +-
wrangler.jsonc | 10 +--
41 files changed, 98 insertions(+), 95 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1357315227..86fc7d6722 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -169,7 +169,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 45
env:
- VITE_GITTENSORY_API_ORIGIN: https://gittensory-api.aethereal.dev
+ VITE_GITTENSORY_API_ORIGIN: https://api.loopover.ai
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
@@ -422,7 +422,7 @@ jobs:
matrix:
shard: [1, 2, 3, 4, 5, 6]
env:
- VITE_GITTENSORY_API_ORIGIN: https://gittensory-api.aethereal.dev
+ VITE_GITTENSORY_API_ORIGIN: https://api.loopover.ai
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
diff --git a/.github/workflows/release-selfhost.yml b/.github/workflows/release-selfhost.yml
index 05e5988de7..238bb378c9 100644
--- a/.github/workflows/release-selfhost.yml
+++ b/.github/workflows/release-selfhost.yml
@@ -351,7 +351,7 @@ jobs:
docker pull ghcr.io/${REPOSITORY_OWNER_LOWER}/loopover-selfhost:${RELEASE_TAG}
\`\`\`
- Multi-arch (linux/amd64 + linux/arm64). See https://gittensory.aethereal.dev/docs/maintainer-self-hosting for setup.
+ Multi-arch (linux/amd64 + linux/arm64). See https://loopover.ai/docs/maintainer-self-hosting for setup.
Includes the Claude Code / Codex subscription CLIs by default; credentials stay runtime-only.
Sentry release id baked into the image: \`${RELEASE_ID}\`.
diff --git a/.github/workflows/ui-deploy.yml b/.github/workflows/ui-deploy.yml
index 0d0edfa776..a8ca4fbf30 100644
--- a/.github/workflows/ui-deploy.yml
+++ b/.github/workflows/ui-deploy.yml
@@ -36,5 +36,5 @@ jobs:
# regen (mirrors the same fix in .github/workflows/ci.yml's "UI build" step).
- name: Validate frontend
env:
- VITE_GITTENSORY_API_ORIGIN: https://gittensory-api.aethereal.dev
+ VITE_GITTENSORY_API_ORIGIN: https://api.loopover.ai
run: npm run ui:openapi:check && npm run ui:lint && npm run ui:typecheck && npm run extension:lint && npm run miner-extension:lint && npm run extension:typecheck && npm run miner-extension:typecheck && npm run extension:build && npm run miner-extension:build && npm --workspace @jsonbored/gittensory-ui run build
diff --git a/.github/workflows/ui-preview-deploy.yml b/.github/workflows/ui-preview-deploy.yml
index d8740ec169..14ce18318e 100644
--- a/.github/workflows/ui-preview-deploy.yml
+++ b/.github/workflows/ui-preview-deploy.yml
@@ -143,7 +143,7 @@ jobs:
}
},
"vars": {
- "VITE_GITTENSORY_API_ORIGIN": "https://gittensory-api.aethereal.dev"
+ "VITE_GITTENSORY_API_ORIGIN": "https://api.loopover.ai"
},
"main": "index.mjs",
"assets": {
diff --git a/.github/workflows/ui-preview.yml b/.github/workflows/ui-preview.yml
index bfdb8010d0..f132bd51ce 100644
--- a/.github/workflows/ui-preview.yml
+++ b/.github/workflows/ui-preview.yml
@@ -47,7 +47,7 @@ jobs:
- name: Build UI
env:
- VITE_GITTENSORY_API_ORIGIN: https://gittensory-api.aethereal.dev
+ VITE_GITTENSORY_API_ORIGIN: https://api.loopover.ai
# Preview-only: enables the synthetic demo session (useSession().signInPreview) so reviewbot can
# screenshot the authenticated /app/* dashboard via ?preview=1 instead of the sign-in wall. The
# production build (ui-deploy.yml) does NOT set this, so the escape hatch is dead-code-eliminated
diff --git a/apps/gittensory-ui/public/openapi.json b/apps/gittensory-ui/public/openapi.json
index 3460e0f059..98ae47ca7c 100644
--- a/apps/gittensory-ui/public/openapi.json
+++ b/apps/gittensory-ui/public/openapi.json
@@ -17075,7 +17075,7 @@
},
"servers": [
{
- "url": "https://gittensory-api.aethereal.dev",
+ "url": "https://api.loopover.ai",
"description": "Production"
}
]
diff --git a/apps/gittensory-ui/public/robots.txt b/apps/gittensory-ui/public/robots.txt
index 06f6932e2b..954e61fd1d 100644
--- a/apps/gittensory-ui/public/robots.txt
+++ b/apps/gittensory-ui/public/robots.txt
@@ -1,4 +1,4 @@
User-agent: *
Allow: /
-Sitemap: https://gittensory.aethereal.dev/sitemap.xml
+Sitemap: https://loopover.ai/sitemap.xml
diff --git a/apps/gittensory-ui/public/sitemap.xml b/apps/gittensory-ui/public/sitemap.xml
index 35e6313b34..da9be0522c 100644
--- a/apps/gittensory-ui/public/sitemap.xml
+++ b/apps/gittensory-ui/public/sitemap.xml
@@ -1,38 +1,38 @@
--stdio is the default recommendation. It keeps auth + analysis on your
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-github-app.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-github-app.tsx
index 4e64755096..020bc94703 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-github-app.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-github-app.tsx
@@ -217,7 +217,7 @@ GITHUB_WEBHOOK_SECRET=diff --git a/apps/gittensory-ui/src/routes/docs.troubleshooting.tsx b/apps/gittensory-ui/src/routes/docs.troubleshooting.tsx index 945602fcc3..7ba48e7411 100644 --- a/apps/gittensory-ui/src/routes/docs.troubleshooting.tsx +++ b/apps/gittensory-ui/src/routes/docs.troubleshooting.tsx @@ -39,7 +39,7 @@ gittensory-mcp status gittensory-mcp whoami`} />
Or hit the public API endpoint directly to confirm reachability:
-@@ -111,7 +111,7 @@ OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318`}
/health couldn't be reached at all. This usually means a network problem
between you and the API edge. Try the Recheck button in the banner, or run{" "}
- curl https://gittensory-api.aethereal.dev/health from your machine to confirm.
+ curl https://api.loopover.ai/health from your machine to confirm.
diff --git a/apps/gittensory-ui/src/routes/index.tsx b/apps/gittensory-ui/src/routes/index.tsx
index 5f5f5c5e2b..a6d1d25157 100644
--- a/apps/gittensory-ui/src/routes/index.tsx
+++ b/apps/gittensory-ui/src/routes/index.tsx
@@ -506,7 +506,7 @@ args = ["-y", "@jsonbored/gittensory-mcp@latest", "--stdio"]`,
audience: "Agents",
filename: "endpoint",
lang: "http",
- snippet: `https://gittensory-api.aethereal.dev/mcp`,
+ snippet: `https://api.loopover.ai/mcp`,
},
];
diff --git a/apps/gittensory-ui/wrangler.jsonc b/apps/gittensory-ui/wrangler.jsonc
index 68a9b13fd2..f0d6589435 100644
--- a/apps/gittensory-ui/wrangler.jsonc
+++ b/apps/gittensory-ui/wrangler.jsonc
@@ -20,7 +20,7 @@
},
},
"vars": {
- "VITE_GITTENSORY_API_ORIGIN": "https://gittensory-api.aethereal.dev",
+ "VITE_GITTENSORY_API_ORIGIN": "https://api.loopover.ai",
},
"routes": [
{
diff --git a/scripts/smoke-production.mjs b/scripts/smoke-production.mjs
index 0d4a11bd0d..5b527083e3 100644
--- a/scripts/smoke-production.mjs
+++ b/scripts/smoke-production.mjs
@@ -1,5 +1,5 @@
-const siteOrigin = normalizeOrigin(process.env.GITTENSORY_SITE_ORIGIN ?? "https://gittensory.aethereal.dev");
-const apiOrigin = normalizeOrigin(process.env.GITTENSORY_API_ORIGIN ?? "https://gittensory-api.aethereal.dev");
+const siteOrigin = normalizeOrigin(process.env.GITTENSORY_SITE_ORIGIN ?? "https://loopover.ai");
+const apiOrigin = normalizeOrigin(process.env.GITTENSORY_API_ORIGIN ?? "https://api.loopover.ai");
const siteRoutes = [
"/",
@@ -27,7 +27,7 @@ async function main() {
for (const route of siteRoutes) checks.push(checkStatus(`${siteOrigin}${route}`, 200, `UI ${route}`));
checks.push(checkStatus(`${siteOrigin}/openapi.json`, 200, "UI OpenAPI artifact", { contentType: /application\/json/i }));
checks.push(checkStatus(`${siteOrigin}/downloads/loopover-extension.zip`, 200, "extension zip", { contentType: /application\/zip/i }));
- checks.push(checkText(`${siteOrigin}/robots.txt`, "Sitemap: https://gittensory.aethereal.dev/sitemap.xml", "robots sitemap directive"));
+ checks.push(checkText(`${siteOrigin}/robots.txt`, "Sitemap: https://loopover.ai/sitemap.xml", "robots sitemap directive"));
checks.push(checkStatus(`${siteOrigin}/sitemap.xml`, 200, "sitemap", { contentType: /application\/xml/i }));
checks.push(checkStatus(`${siteOrigin}/CNAME`, 404, "retired GitHub Pages CNAME"));
diff --git a/scripts/smoke-ui-browser.mjs b/scripts/smoke-ui-browser.mjs
index 43258ed887..ddc8813b67 100644
--- a/scripts/smoke-ui-browser.mjs
+++ b/scripts/smoke-ui-browser.mjs
@@ -1,4 +1,4 @@
-const siteOrigin = new URL(process.env.GITTENSORY_SITE_ORIGIN ?? "https://gittensory.aethereal.dev").origin;
+const siteOrigin = new URL(process.env.GITTENSORY_SITE_ORIGIN ?? "https://loopover.ai").origin;
const routes = ["/", "/app", "/app/workbench", "/app/repos", "/app/runs", "/app/analytics", "/app/operator", "/app/commands", "/app/digest", "/api", "/roadmap", "/changelog", "/extension", "/docs"];
const playwright = await import("playwright").catch(() => null);
diff --git a/scripts/write-ui-openapi.ts b/scripts/write-ui-openapi.ts
index 0d73a3d411..93410e939a 100644
--- a/scripts/write-ui-openapi.ts
+++ b/scripts/write-ui-openapi.ts
@@ -9,7 +9,7 @@ const target = resolve(root, "apps/gittensory-ui/public/openapi.json");
const checkOnly = process.argv.includes("--check");
const spec = buildOpenApiSpec();
-spec.servers = [{ url: "https://gittensory-api.aethereal.dev", description: "Production" }];
+spec.servers = [{ url: "https://api.loopover.ai", description: "Production" }];
const current = await readFile(target, "utf8").catch(() => "");
const currentSpec = parseCurrentSpec(current);
diff --git a/src/api/routes.ts b/src/api/routes.ts
index 8f7f3eec2f..a8ef612eef 100644
--- a/src/api/routes.ts
+++ b/src/api/routes.ts
@@ -4211,7 +4211,7 @@ const APP_COMMANDS = [
] as const;
function authRedirectWithError(env: Env, reason: string): string {
- const siteOrigin = env.PUBLIC_SITE_ORIGIN ?? "https://gittensory.aethereal.dev";
+ const siteOrigin = env.PUBLIC_SITE_ORIGIN ?? "https://loopover.ai";
const url = new URL("/app", siteOrigin);
url.searchParams.set("auth", "error");
url.searchParams.set("reason", reason);
@@ -5700,6 +5700,7 @@ function requiresApiToken(path: string): boolean {
}
const DEFAULT_CORS_ORIGINS = [
+ "https://loopover.ai",
"https://gittensory.aethereal.dev",
"http://localhost:3000",
"http://localhost:4173",
diff --git a/src/auth/github-oauth.ts b/src/auth/github-oauth.ts
index 74bab5cf22..5cf8448ad0 100644
--- a/src/auth/github-oauth.ts
+++ b/src/auth/github-oauth.ts
@@ -236,12 +236,12 @@ function githubOAuthCallbackUrl(env: Env, requestUrl: string): string {
}
function normalizeReturnTo(env: Env, value: string | undefined): string {
- const siteOrigin = env.PUBLIC_SITE_ORIGIN ?? "https://gittensory.aethereal.dev";
+ const siteOrigin = env.PUBLIC_SITE_ORIGIN ?? "https://loopover.ai";
const fallback = `${siteOrigin.replace(/\/$/, "")}/app`;
if (!value) return fallback;
try {
const url = new URL(value, siteOrigin);
- // siteOrigin already IS "https://gittensory.aethereal.dev" when PUBLIC_SITE_ORIGIN is unset (the fallback
+ // siteOrigin already IS "https://loopover.ai" when PUBLIC_SITE_ORIGIN is unset (the fallback
// two lines up), so a separate hardcoded entry here was dead weight once a self-hoster sets their own
// PUBLIC_SITE_ORIGIN -- it kept accepting the cloud origin as a valid redirect target even for a self-host
// instance that never uses it (#4615). Rely solely on siteOrigin.
diff --git a/src/env.d.ts b/src/env.d.ts
index 91afc01ace..d6509cb10f 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -178,7 +178,7 @@ declare global {
* engine sources GitHub installation tokens from the central Orb (POST /v1/orb/token) instead of a local App
* key. Cloud never sets it ⇒ inert there. See src/orb/broker-client. (A secret — never commit a real value.) */
ORB_ENROLLMENT_SECRET?: string;
- /** Override the Orb broker base URL the self-host client calls (default https://gittensory-api.aethereal.dev);
+ /** Override the Orb broker base URL the self-host client calls (default https://api.loopover.ai);
* point at a private gittensory deployment if you self-host the broker too. */
ORB_BROKER_URL?: string;
/** The retired review App's own credentials. Optional: that App has been fully deleted — cloud no longer
diff --git a/src/github/footer.ts b/src/github/footer.ts
index 1833a71f30..77c1903efe 100644
--- a/src/github/footer.ts
+++ b/src/github/footer.ts
@@ -11,7 +11,7 @@
/** The LoopOver product site (marketing on-ramp / attribution target) -- the DEFAULT only. A
* self-hoster with `PUBLIC_SITE_ORIGIN` set gets their own domain instead, both here and in
* `gittensoryFooter` below (#4613). */
-export const GITTENSORY_SITE_URL = "https://gittensory.aethereal.dev";
+export const GITTENSORY_SITE_URL = "https://loopover.ai";
/** Minimal env slice `gittensoryFooter` needs, narrowed from the full `Env` the same way this file's
* `maintainerControlPanelUrl` already narrows its own `env` param inline -- so every file that renders
diff --git a/src/github/repo-doc-pr.ts b/src/github/repo-doc-pr.ts
index b36e2dbb9f..2edc318b19 100644
--- a/src/github/repo-doc-pr.ts
+++ b/src/github/repo-doc-pr.ts
@@ -150,7 +150,7 @@ export async function openRepoDocPullRequest(env: Env, repoFullName: string, mod
const profile = await extractRepoProfile(env, repoFullName);
if (!profile.present) return { opened: false, reason: profile.reason };
// #4613: a self-hoster's own domain (env.PUBLIC_SITE_ORIGIN) reaches the generated AGENTS.md's
- // attribution link instead of gittensory.aethereal.dev -- same fallback `maintainerControlPanelUrl`/
+ // attribution link instead of loopover.ai -- same fallback `maintainerControlPanelUrl`/
// `gittensoryFooter` already use.
const generatedSection = renderRepoDocContent(profile, env.PUBLIC_SITE_ORIGIN ?? GITTENSORY_SITE_URL);
if (!generatedSection) return { opened: false, reason: "no content rendered from profile" };
diff --git a/src/orb/broker-client.ts b/src/orb/broker-client.ts
index 09f4d87b9b..caa0705b06 100644
--- a/src/orb/broker-client.ts
+++ b/src/orb/broker-client.ts
@@ -10,7 +10,7 @@
import { incr } from "../selfhost/metrics";
/** The Orb's hosted broker base; override (ORB_BROKER_URL) only to point at a private gittensory deployment. */
-const DEFAULT_BROKER_URL = "https://gittensory-api.aethereal.dev";
+const DEFAULT_BROKER_URL = "https://api.loopover.ai";
// The broker's cold token mint can take many seconds when GitHub is throttling the App; allow headroom so the one
// uncached mint completes and populates the broker-side cache (steady-state cache hits return in well under a second).
const BROKER_TIMEOUT_MS = 25_000;
diff --git a/src/review/repo-doc-render.ts b/src/review/repo-doc-render.ts
index b7891b929b..c0b0e1d0b6 100644
--- a/src/review/repo-doc-render.ts
+++ b/src/review/repo-doc-render.ts
@@ -66,7 +66,7 @@ function renderCiWorkflowFiles(ciWorkflowFiles: string[]): string {
* is therefore never touched.
*
* `siteUrl` is resolved by the caller from `env.PUBLIC_SITE_ORIGIN ?? GITTENSORY_SITE_URL` (#4613), so a
- * self-hoster's own domain reaches the attribution link instead of `gittensory.aethereal.dev`. Threading it as a
+ * self-hoster's own domain reaches the attribution link instead of `loopover.ai`. Threading it as a
* plain string argument (rather than an `env` slice) keeps this function's purity/determinism contract intact --
* the SAME `(profile, siteUrl)` pair always renders the SAME output, which is all the diff-aware refresh in
* `generated-doc-refresh.ts` needs for its byte-for-byte comparison.
diff --git a/src/review/visual/capture.ts b/src/review/visual/capture.ts
index fb8ae6c625..c54fe71732 100644
--- a/src/review/visual/capture.ts
+++ b/src/review/visual/capture.ts
@@ -488,7 +488,7 @@ export async function buildCapture(env: Env, token: string, target: CaptureTarge
const repo = parseRepo(target.repoFullName);
const apiVersion = "2022-11-28";
// before = production. review.visual.production_url (#3611 follow-up) ALWAYS wins when set -- PUBLIC_SITE_ORIGIN
- // is a single GLOBAL env var (e.g. https://gittensory.aethereal.dev) with no per-repo awareness, correct for at
+ // is a single GLOBAL env var (e.g. https://loopover.ai) with no per-repo awareness, correct for at
// most one repo on a multi-repo self-host instance; every other repo needs its own override here.
const prodBase = visualConfig?.productionUrl ? visualConfig.productionUrl : (env.PUBLIC_SITE_ORIGIN ?? "");
diff --git a/src/selfhost/orb-collector.ts b/src/selfhost/orb-collector.ts
index c425ec6792..7b78885fa4 100644
--- a/src/selfhost/orb-collector.ts
+++ b/src/selfhost/orb-collector.ts
@@ -169,7 +169,7 @@ export async function exportOrbBatch(db: D1Database, batchSize = 200, fetchFn: t
// instance's DEDICATED anonymization secret (a 256-bit random key generated once and persisted in
// system_flags — see getOrCreateAnonSecret), single-purpose and never the App key, so the collector
// (which never holds it) can never de-anonymize them.
- const collectorUrl = process.env.ORB_COLLECTOR_URL ?? "https://gittensory-api.aethereal.dev/v1/orb/ingest";
+ const collectorUrl = process.env.ORB_COLLECTOR_URL ?? "https://api.loopover.ai/v1/orb/ingest";
const secret = await getOrCreateAnonSecret(db);
const anonymize = (process.env.ORB_ANONYMIZE ?? "true").toLowerCase() !== "false";
const instance = instanceId(secret);
diff --git a/src/signals/engine.ts b/src/signals/engine.ts
index 1daa134eb9..2d10d26ba8 100644
--- a/src/signals/engine.ts
+++ b/src/signals/engine.ts
@@ -4156,7 +4156,7 @@ type PublicSafeCollapsibleArgs = {
e2eTestGenAvailable?: boolean | undefined;
/** #5078: resolved by the caller from `env.PUBLIC_SITE_ORIGIN`, same as `buildPublicPrIntelligenceComment`'s
* own `env` param -- lets the "[BETA] Chat with Gittensory" collapsible link to a self-hoster's own
- * command-reference doc page instead of always the canonical gittensory.aethereal.dev. */
+ * command-reference doc page instead of always the canonical loopover.ai. */
env: GittensoryFooterEnv;
};
diff --git a/test/helpers/d1.ts b/test/helpers/d1.ts
index d7d794aab5..49facb83b8 100644
--- a/test/helpers/d1.ts
+++ b/test/helpers/d1.ts
@@ -89,7 +89,7 @@ export function createTestEnv(overrides: Partial