diff --git a/.changeset/one-origin-dev-configs-5745.md b/.changeset/one-origin-dev-configs-5745.md new file mode 100644 index 0000000000..a9ed98970c --- /dev/null +++ b/.changeset/one-origin-dev-configs-5745.md @@ -0,0 +1,36 @@ +--- +--- + +Dev/build configuration only — this publishes nothing, declared explicitly with an +empty frontmatter rather than left undeclared. No package `src/` is touched. The +files changed are `apps/console/.env.development`, `examples/console-starter/.env.*` +and `examples/console-starter/vite.config.ts`; `.env.development` is not part of +`@object-ui/console`'s published artifact (`files: ["dist"]`, built from +`.env.production`, which is untouched), and the example is private and on the +changeset `ignore` list. + +Converge the dev stacks on one origin: empty `VITE_SERVER_URL` and let the Vite +proxy do the split-host hop. + +Both dev env files pointed `VITE_SERVER_URL` at `http://localhost:3000` while the +page was served from `:5180` (console) and `:5173` (starter). Every client in these +apps coalesces an unset value to `''` and then builds a relative `/api/...` URL, so +an empty value routes same-origin through the dev proxy instead. `console-starter` +had no `server` block at all, so it gets the same one-stanza `/api` proxy +(`DEV_PROXY_TARGET` or `http://localhost:3000`) that `apps/console` already carries; +no port is pinned, because its README documents the example on Vite's default 5173. + +This is the ruled prerequisite for the `sameOriginOnly` action-runtime default. Under +that default a non-empty `VITE_SERVER_URL` makes every relative-target `type: 'api'` +action resolve cross-origin and be fetched bare — no `Authorization`, no +`X-Tenant-ID`, no `Accept-Language` — i.e. a 401 for the standard `pnpm dev` stack. + +`examples/console-starter/.env.production` is emptied too. Its committed +`https://demo.objectstack.ai` was not a deliberate choice for the example: it is the +same pre-convergence value the sibling console carried until 2026-05-24, when +`c351c9604` ("default published SPA to same-origin") cleared it there for CORS-blank +-page reasons and touched only `apps/console/.env.production`. The starter was left +behind by that commit, and as a fork-ready scaffold its committed value is the +production origin every fork inherits. Split-origin deployments inject +`VITE_SERVER_URL` at build time, the way the console's own `.env.production` already +documents. diff --git a/apps/console/.env.development b/apps/console/.env.development index 54c6cd0db1..aa6b3b0841 100644 --- a/apps/console/.env.development +++ b/apps/console/.env.development @@ -1,5 +1,20 @@ -# Development: connect to a local or remote ObjectStack server. -# Set VITE_SERVER_URL to your dev server (e.g. http://localhost:3000 or https://demo.objectstack.ai). -# Leave empty to use same-origin (when Console is served by the server itself). -VITE_SERVER_URL=http://localhost:3000 +# Development: the Console SPA and the ObjectStack API share ONE origin. +# +# Leave VITE_SERVER_URL EMPTY. Every client in this app coalesces an unset +# value to '' and then builds a RELATIVE `/api/...` URL, which the Vite dev +# server proxies to the backend (`vite.config.ts` -> server.proxy['/api'], +# target `DEV_PROXY_TARGET` or http://localhost:3000). So the page at :5180 +# fetches :5180 and the proxy does the split-host hop. +# +# Why empty rather than `http://localhost:3000`: a non-empty value makes every +# relative-target `type: 'api'` action resolve CROSS-origin, and the action +# runtime does not attach Authorization / X-Tenant-ID / Accept-Language to a +# cross-origin fetch — a 401 on the standard `pnpm dev` stack. Same-origin is +# also what `.env.production` already ships, so dev now matches prod. +# +# Pointing at a DIFFERENT backend: set DEV_PROXY_TARGET (keeps one origin) — +# DEV_PROXY_TARGET=https://demo.objectstack.ai pnpm dev +# Setting VITE_SERVER_URL to an absolute origin still works, but it opts this +# app back out of same-origin and into CORS + the 401 above. +VITE_SERVER_URL= VITE_USE_MOCK_SERVER=false diff --git a/examples/console-starter/.env.development b/examples/console-starter/.env.development index 54c6cd0db1..5d3cf0f758 100644 --- a/examples/console-starter/.env.development +++ b/examples/console-starter/.env.development @@ -1,5 +1,18 @@ -# Development: connect to a local or remote ObjectStack server. -# Set VITE_SERVER_URL to your dev server (e.g. http://localhost:3000 or https://demo.objectstack.ai). -# Leave empty to use same-origin (when Console is served by the server itself). -VITE_SERVER_URL=http://localhost:3000 +# Development: the starter SPA and the ObjectStack API share ONE origin. +# +# Leave VITE_SERVER_URL EMPTY. `src/App.tsx`, `src/main.tsx` and everything +# inside @object-ui/app-shell coalesce an unset value to '' and then build a +# RELATIVE `/api/...` URL, which the Vite dev server proxies to the backend +# (`vite.config.ts` -> server.proxy['/api'], target `DEV_PROXY_TARGET` or +# http://localhost:3000). So the page at :5173 fetches :5173 and the proxy +# does the split-host hop. +# +# Why empty rather than `http://localhost:3000`: a non-empty value makes every +# relative-target `type: 'api'` action resolve CROSS-origin, and the action +# runtime does not attach Authorization / X-Tenant-ID / Accept-Language to a +# cross-origin fetch — a 401 on the standard `pnpm dev` stack. +# +# Pointing at a DIFFERENT backend: set DEV_PROXY_TARGET (keeps one origin) — +# DEV_PROXY_TARGET=https://demo.objectstack.ai pnpm dev +VITE_SERVER_URL= VITE_USE_MOCK_SERVER=false diff --git a/examples/console-starter/.env.production b/examples/console-starter/.env.production index 19427733c1..f93a7febbd 100644 --- a/examples/console-starter/.env.production +++ b/examples/console-starter/.env.production @@ -1,4 +1,18 @@ # Production: connect to the ObjectStack server. -# Set VITE_SERVER_URL to the server origin, or leave empty for same-origin. -VITE_SERVER_URL=https://demo.objectstack.ai +# +# Defaults to same-origin (empty), matching `apps/console/.env.production`. +# This is a FORK-READY SCAFFOLD: whatever is committed here is the production +# origin every fork inherits, so it must not name one specific deployment. +# A committed `https://demo.objectstack.ai` was cleared from the sibling +# console for exactly that reason — it CORS-blocked the auth / i18n / +# discovery calls and left a blank page on every host but the demo one. +# +# Deployments that genuinely need a split origin inject VITE_SERVER_URL from +# their own deploy environment at build time, rather than committing it: +# +# VITE_SERVER_URL=https://your-objectstack-host pnpm build +# +# A split origin also requires the backend to allow CORS from the SPA origin +# and to issue auth cookies as `SameSite=None; Secure`. +VITE_SERVER_URL= VITE_USE_MOCK_SERVER=false diff --git a/examples/console-starter/vite.config.ts b/examples/console-starter/vite.config.ts index d8be2bdac2..11467d364b 100644 --- a/examples/console-starter/vite.config.ts +++ b/examples/console-starter/vite.config.ts @@ -60,4 +60,24 @@ const workspaceAliases: Record = { export default defineConfig({ plugins: [react()], resolve: { alias: workspaceAliases }, + // ONE ORIGIN in dev, proxy for the split host. `.env.development` leaves + // VITE_SERVER_URL empty, so every client here builds a RELATIVE `/api/...` + // URL and this stanza forwards it to the backend. Without it an empty + // VITE_SERVER_URL would make those fetches hit the Vite dev server, which + // has no `/api` route and answers with the SPA's index.html fallback. + // + // Why not just point VITE_SERVER_URL at the backend: a cross-origin value + // makes every relative-target `type: 'api'` action resolve off-origin, and + // the action runtime does not attach Authorization / X-Tenant-ID / + // Accept-Language across origins — a 401 on the standard `pnpm dev` stack. + // + // `DEV_PROXY_TARGET` and the `/api` prefix mirror `apps/console/vite.config.ts` + // deliberately: the two dev stacks are meant to be configured the same way. + // No `port` is set — the README documents this example on Vite's default + // 5173, and pinning one would only collide with a real fork's choice. + server: { + proxy: { + '/api': { target: process.env.DEV_PROXY_TARGET || 'http://localhost:3000', changeOrigin: true }, + }, + }, });