Skip to content

Commit 4fe3af9

Browse files
docs(deployment): branding env vars follow the runtime — OS_PRODUCT_NAME defaults to ObjectOS, six readers added, precedence per posture (#24990)
Fixes #24887 Clause-②: no ## What changes `content/docs/deployment/environment-variables.mdx` only: the branding section. Docs follow the implementation. Every default, fallback and reader below is read off `main`'s code (objectstack `e4d604efd3`, which this branch carries; console consumers read in objectui at the `.objectui-sha` pin `cf153eb2e535`). The section heading loses "(single-tenant)", because under the walled postures these variables are the console's only branding source. No page or package links the old `#branding-single-tenant` anchor (`git grep` over `content`, `apps`, `packages`, `docs` and `skills` finds 0), and `check:doc-anchors` is green. | Variable | Old row → new row | Reader (`file#symbol`) | Default | |:--|:--|:--|:--| | `OS_PRODUCT_NAME` | Old: default `ObjectStack`, "shown in the UI chrome and emails". New: names the console only. That is the browser-tab title (alone, or after the open app's label), the header's product mark, the sign-in pages, the loading screen and the PWA manifest `name`. It names no mail and no auth, which point to `OS_APP_NAME`. | `packages/cloud-connection/src/runtime-config-plugin.ts#RuntimeConfigPlugin.constructor` (`productName`), served by `#RuntimeConfigPlugin.brandingBlock`. Console: objectui `packages/app-shell/src/runtime-config.ts#getProductName`, read by `apps/console/src/main.tsx`, `apps/console/index.html`, `layout/ConsoleLayout.tsx`, `layout/AppHeader.tsx#ProductHomeMark`, `pages/auth/AuthLayout.tsx`, `pages/auth/DeviceAuthPage.tsx` and `chrome/LoadingScreen.tsx` | `ObjectOS` | | `OS_PRODUCT_SHORT_NAME` | Old: default `OS`, "favicons, mobile titles". New: the PWA manifest `short_name`, falling back to the product name served. | `#RuntimeConfigPlugin.constructor` (`configuredShortName`), `#RuntimeConfigPlugin.brandingBlock`. Console: `apps/console/src/main.tsx` into objectui `packages/mobile/src/pwa.ts#generatePWAManifest` | the product name served | | `OS_PRODUCT_STAGE` | Absent → added. `preview` / `beta` / `ga`, matched exactly. Drives the top-bar chip. Unset sends no `stage` key and the console keeps `preview`. Any other value gets one `warn` at boot and is treated as unset. | `#RuntimeConfigPlugin.constructor`, `#asPlatformStage`, refusal in `#RuntimeConfigPlugin.start`. Console: objectui `layout/PreviewBadge.tsx#PreviewBadge` | no `stage` key | | `OS_LOGO_URL` | Absent → added. Sign-in pages, loading screen, header mark. | `#RuntimeConfigPlugin.constructor` (`logoUrl`), `#brandingBlock` | none | | `OS_FAVICON_URL` | Absent → added. Tab icon and PWA manifest icon. An app's own `branding.favicon` replaces it while that app is open. No setting outranks it. | `#RuntimeConfigPlugin.constructor` (`faviconUrl`). Console: `apps/console/index.html`, `apps/console/src/main.tsx` | none | | `OS_BRAND_COLOR` | Absent → added. Reaches the console only as `OS_PWA_THEME_COLOR`'s default. App `AppBranding` colours are separate. | `#RuntimeConfigPlugin.constructor` (`brandColor`), `#brandingBlock` | none | | `OS_PWA_DESCRIPTION` | Absent → added. PWA manifest `description`. | `#RuntimeConfigPlugin.constructor` (`configuredPwaDescription`), `#brandingBlock` | the served product name followed by ` — runtime console` | | `OS_PWA_THEME_COLOR` | Absent → added. PWA manifest `theme_color`. | `#RuntimeConfigPlugin.constructor` (`configuredPwaThemeColor`), `#brandingBlock` | the served brand colour, else `#4f46e5` | | `OS_APP_NAME` | Old last sentence: "An explicitly set `branding.workspace_name` setting outranks it in auth." New: true under `single` only, and the authenticator issuer takes a change only when the auth instance is rebuilt. Under `group` / `isolated` nothing outranks it. It never names the console. | `packages/plugins/plugin-email/src/capability-arg.ts#resolveDeploymentAppName`, passed in `packages/cli/src/commands/serve.ts` (`new AuthPlugin({ appName })`). Override binding: `packages/plugins/plugin-auth/src/auth-plugin.ts` (the `brandingOffered` test before `applyBrand`). `packages/plugins/plugin-auth/src/auth-manager.ts#getAppName` / `#setAppName`, and the issuer read in `createAuthInstance` | `ObjectStack` (unchanged) | All eight `OS_PRODUCT_*` / `OS_LOGO_URL` / `OS_FAVICON_URL` / `OS_BRAND_COLOR` / `OS_PWA_*` variables are read **once, in the constructor**, so a change takes a restart. The page now says so. A host's own `RuntimeConfigPlugin` option of the same name outranks each one. The CLI passes none: `packages/cli/src/commands/serve.ts#Serve.RUNTIME_CONFIG_OPTIONS` carries `controlPlaneUrl`, `singleEnvironment` and `installLocal` only. The showcase's own mount passes none either. ## Precedence, per posture (PR #24959, re-read on `main`) Under `single`, three served fields read the `branding` settings namespace per request (`#RuntimeConfigPlugin.readSavedBranding`, `#BRANDING_SETTINGS_KEYS`): `workspace_name` → `productName`, `logo_url` → `logoUrl`, `accent_color` → `brandColor`. For each, the first source that is set wins: 1. the setting: `OS_BRANDING_WORKSPACE_NAME` / `OS_BRANDING_LOGO_URL` / `OS_BRANDING_ACCENT_COLOR` (`packages/services/service-settings/src/settings-service.types.ts#envKeyOf`; it outranks a stored row and locks the field), then a value saved in Setup → Branding; 2. the host's `RuntimeConfigPlugin` option; 3. `OS_PRODUCT_NAME` / `OS_LOGO_URL` / `OS_BRAND_COLOR`; 4. the built-in default. A value with `source: 'default'` is skipped, so the Branding manifest's own `ObjectStack` is never served to the console. A set `workspace_name` also names sign-in mail, the authenticator issuer and MCP sign-in, ahead of `OS_APP_NAME`. Under `group` / `isolated`, `SettingsServicePlugin` holds `branding` back (`packages/services/service-settings/src/settings-service-plugin.ts#UNWALLED_ONLY_NAMESPACES`, `#offerUnwalledOnlyNamespaces`). The namespace is absent from `GET /api/settings`, `GET` and `PUT` on it answer `404 UNKNOWN_NAMESPACE`, and Setup lists no Branding entry. The `OS_BRANDING_*` variables reach no reader. The console's branding is the host option, then the variables, then the default. The auth product name comes from the `OS_APP_NAME` chain alone. The migration the changesets state, `OS_BRANDING_WORKSPACE_NAME=Acme` → `OS_APP_NAME=Acme`, is on the page. ## Live reading The showcase was booted on this branch's base (`7adfaec2a1`) with `pnpm dev -- --fresh -p 41873`. `examples/app-showcase` mounts its own `RuntimeConfigPlugin({ controlPlaneUrl: '', singleEnvironment: true, installLocal: true })`, with no branding option. None of the eight variables, `OS_APP_NAME`, `OS_BRANDING_*`, `OS_TENANCY_POSTURE` or `OS_MULTI_ORG_ENABLED` was set; each was checked set/unset without printing a value. The posture is `single`. - `GET /api/v1/runtime/config` answered `200`, with `branding` = `{"productName":"ObjectOS","productShortName":"ObjectOS","pwaDescription":"ObjectOS — runtime console","pwaThemeColor":"#4f46e5"}`. There is no `stage`, `logoUrl`, `faviconUrl` or `brandColor` key. - `GET /api/settings/branding` (as the seeded admin) answered `200`: `workspace_name` = `ObjectStack` with `source: default`, and `accent_color` / `logo_url` = null with `source: default`. So the namespace is offered under `single`, and its default did not reach the console. - The sign-in mail: `POST /api/v1/auth/request-password-reset` for the seeded admin answered `200`, and its `sys_identity_email` row reads subject `Reset your ObjectStack password`, status `sent`. The body names `ObjectStack` once and `ObjectOS` zero times. That is the two-names reading the card carries: `ObjectOS` in the console and `ObjectStack` in the mail, on one unconfigured deployment. ## Verification (HEAD `d3a8afd655`) - Gate families: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 41 commands from this tree. All 41 ran with exit 0, each code captured before any pipe. `--ran` reports: `41 derived famil(ies) accounted for — 41 run, 0 NOT-MEASURED`. Among them: `check:doc-anchors` (563 links resolve), `check:docs-single-h1`, `check:doc-frontmatter`, `check:docs-redirects`, `check:nul-bytes` (no raw control bytes), and `@objectstack/spec check:docs` (227 generated files in sync). - The page compiles with the docs app's own `@mdx-js/mdx` 3.1.1 + `remark-gfm` 4.0.1 (exit 0). - `git merge-tree --write-tree origin/main HEAD` exit 0, tree `e6b183653f`. The path is not routed to the regen driver. - No package is touched, so there is no build closure, package test or typecheck to run. Left to CI and not measured locally: `Build Docs`, the `Test Core` shards and the type-check lanes. - `skip-changeset`, measured: no published package's `files[]` names `content`, `docs` or an mdx glob (73 published packages). A phrase unique to this diff (`Branding precedence, by tenancy posture`) has 0 hits across the 205 `files[]` roots. Control: `OS_PWA_THEME_COLOR` has 4 hits, in `packages/cloud-connection/dist`. ## Acceptance notes - Two product names on an unconfigured deployment (`ObjectOS` against `ObjectStack`) is product behaviour, and the card scopes it out. The page now states it, and names `branding.workspace_name` under `single`, or `OS_PRODUCT_NAME` plus `OS_APP_NAME`, as the way to name a deployment one way everywhere. Carrier: none. - Empty-string handling differs between the constructor's reads. `OS_PRODUCT_NAME`, `OS_PRODUCT_SHORT_NAME` and `OS_PRODUCT_STAGE` read empty as unset. `OS_LOGO_URL`, `OS_FAVICON_URL`, `OS_BRAND_COLOR` and the `OS_PWA_*` pair forward `''`, because `??` keeps it. The console's `||` getters then fall back, so nothing visible changes. This is a read-only observation and is not filed. The page claims "empty reads as unset" for the product name only. - No other page in `content/docs` states these defaults. objectui's `packages/app-shell/README.md` names `OS_PRODUCT_STAGE` consistently with this page. --- _Generated by [Claude Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent fde7456 commit 4fe3af9

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

‎content/docs/deployment/environment-variables.mdx‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,28 @@ Auth settings precedence:
129129

130130
---
131131

132-
## Branding (single-tenant)
132+
## Branding
133+
134+
The console's product name, logo, favicon and colours are served by `RuntimeConfigPlugin` (`@objectstack/cloud-connection`) as the `branding` block of `GET /api/v1/runtime/config`, which the console reads on every page load. A host that constructs its own `RuntimeConfigPlugin` with an option of the same name (`productName`, `logoUrl`, …) outranks the variable. The first eight variables below are read **once, when that plugin is constructed at boot**: changing one takes a restart. A value saved in **Setup → Branding** is read per request instead (see the precedence below). Mail and auth are named separately, by `OS_APP_NAME`.
133135

134136
| Variable | Type | Default | Description |
135137
|:---|:---|:---|:---|
136-
| `OS_PRODUCT_NAME` | string | `ObjectStack` | Product name shown in the UI chrome and emails. |
137-
| `OS_PRODUCT_SHORT_NAME` | string | `OS` | Short name used in compact UI surfaces (favicons, mobile titles). |
138-
| `OS_APP_NAME` | string | — | The deployment's product name under `objectstack serve`: `{{appName}}` in every email template (auth emails included), the issuer an authenticator app lists a TOTP enrollment under, and the MCP OAuth resource name. Wins over `config.email.appName`, `config.email.defaultTemplateContext.appName` and `config.appName`; with none of them set the name is `ObjectStack`. An explicitly set `branding.workspace_name` setting outranks it in auth. |
138+
| `OS_PRODUCT_NAME` | string | `ObjectOS` | The console's product name (`branding.productName`): the browser-tab title (alone, or after the open app's label), the header's product mark, the sign-in pages, the loading screen and the installed-app (PWA) manifest's `name`. It does **not** name emails, the authenticator app entry or the MCP sign-in — `OS_APP_NAME` does — so an unconfigured deployment reads `ObjectOS` in the console and `ObjectStack` in its mail. Empty reads as unset. |
139+
| `OS_PRODUCT_SHORT_NAME` | string | the product name served | The PWA manifest's `short_name` (`branding.productShortName`). Unset, it is the product name actually served, which under `single` may be a saved Branding name. |
140+
| `OS_PRODUCT_STAGE` | enum | — (no `stage` key) | `preview` \| `beta` \| `ga`, matched exactly. Drives the console's top-bar chip (`branding.stage`): "Preview" for `preview`, "Beta" for `beta`, none for `ga`. Unset sends no `stage` key and the console keeps its own `preview` default, so the chip shows. Any other value (`GA`, `general-availability`) is refused with one `warn` at boot and treated as unset. |
141+
| `OS_LOGO_URL` | url | — | The product logo, absolute or relative (`branding.logoUrl`): the sign-in pages, the loading screen and the header's product mark. Unset, the console draws its built-in mark. |
142+
| `OS_FAVICON_URL` | url | — | The favicon, absolute or relative (`branding.faviconUrl`): the browser-tab icon and the PWA manifest's icon. While an app with its own `branding.favicon` is open, that icon replaces it. No Branding setting overrides this variable, on any posture. |
143+
| `OS_BRAND_COLOR` | string | — | The brand colour as hex, e.g. `#4F46E5` (`branding.brandColor`). The console uses it as the installed-app theme colour, through `OS_PWA_THEME_COLOR`'s default; each app's own `AppBranding` colours are separate. |
144+
| `OS_PWA_DESCRIPTION` | string | `<product name> — runtime console` | The PWA manifest's `description` (`branding.pwaDescription`). Unset, it is built from the product name actually served. |
145+
| `OS_PWA_THEME_COLOR` | string | the brand colour served, else `#4f46e5` | The PWA manifest's `theme_color` (`branding.pwaThemeColor`), as hex. Unset, it is the brand colour actually served, else `#4f46e5`. |
146+
| `OS_APP_NAME` | string | — | The deployment's product name under `objectstack serve` for mail and auth: `{{appName}}` in every email template (auth emails included), the issuer an authenticator app lists a TOTP enrollment under, and the MCP OAuth resource name. Wins over `config.email.appName`, `config.email.defaultTemplateContext.appName` and `config.appName`; with none of them set the name is `ObjectStack`. Under `single`, an explicitly set `branding.workspace_name` setting outranks it in auth (the authenticator issuer takes a change only when the auth instance is rebuilt, such as at a restart); under `group` / `isolated` nothing does. It never names the console — that is `OS_PRODUCT_NAME`. |
147+
148+
Branding precedence, by tenancy posture (`OS_TENANCY_POSTURE`):
149+
150+
- **`single`** — three fields read the `branding` settings namespace on every request, so a save in **Setup → Branding** shows on the next page load with no restart: `workspace_name` → `productName`, `logo_url` → `logoUrl`, `accent_color` → `brandColor`. Each takes the first source that is set: the setting (a value saved in Setup, or the settings layer's own `OS_BRANDING_WORKSPACE_NAME` / `OS_BRANDING_LOGO_URL` / `OS_BRANDING_ACCENT_COLOR`, which outranks a saved value and locks the field), then the host's `RuntimeConfigPlugin` option, then `OS_PRODUCT_NAME` / `OS_LOGO_URL` / `OS_BRAND_COLOR`, then the built-in default. The Branding page's own default for `workspace_name` (`ObjectStack`) is never served to the console. A set `workspace_name` also names sign-in emails, the authenticator app entry and the MCP sign-in, ahead of `OS_APP_NAME`.
151+
- **`group` / `isolated`** — the `branding` namespace is not offered: it is absent from `GET /api/settings`, `GET` and `PUT /api/settings/branding` answer `404 UNKNOWN_NAMESPACE`, and Setup lists no Branding entry. The `OS_BRANDING_*` variables are not read. The console's branding is the host option, then the variables in this table, then the default, and the auth product name is the `OS_APP_NAME` chain alone. A walled deployment that set `OS_BRANDING_WORKSPACE_NAME` moves the value to `OS_APP_NAME`.
152+
153+
On every posture `productShortName`, `pwaDescription` and `pwaThemeColor` follow the name and colour actually served unless set themselves, and `OS_PRODUCT_STAGE` and `OS_FAVICON_URL` have no setting above them.
139154

140155
---
141156

0 commit comments

Comments
 (0)