# Feature flags and server selection

> Reference: Vite environment overrides, feature flag defaults, local service selection syntax, sync-service host selection, and runtime host helpers.

- Repository: macro-inc/macro
- GitHub: https://github.com/macro-inc/macro
- Human docs: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e
- Complete Markdown: https://grok-wiki.com/public/docs/macro-inc-macro-bb988e1a448e/llms-full.txt

## Source Files

- `js/app/packages/core/constant/featureFlags.ts`
- `js/app/packages/core/tests/featureFlags.test.ts`
- `js/app/packages/core/constant/servers.ts`
- `js/app/justfile`
- `js/app/packages/app/vite.config.ts`
- `js/app/package.json`

---

---
title: "Feature flags and server selection"
description: "Reference: Vite environment overrides, feature flag defaults, local service selection syntax, sync-service host selection, and runtime host helpers."
---

The frontend resolves feature gates and backend hosts at module load from `import.meta.env`, with Vite exposing `VITE_*` variables and `packages/app/vite.base.ts` injecting app metadata such as `__APP_VERSION__`, `ASSETS_PATH`, `__LOCAL_JWT__`, and `__GIT_BRANCH__`.

## Vite mode and environment inputs

`packages/app/vite.config.ts` delegates to `createAppViteConfig()`. The base config sets Vite `mode` from `process.env.MODE ?? mode`, so the shell `MODE` value wins over Vite's default mode.

| Input | Used by | Behavior |
| --- | --- | --- |
| `MODE=development` | feature flags, remote host suffixes, sync-service suffixes, assets path | Enables `DEV_MODE_ENV`, uses `-dev` service hosts, uses sync-service `-dev3`, and allows `VITE_LOCAL_SERVERS` selection. |
| `MODE=production` | feature flags, remote host suffixes, sync-service suffixes | Enables `PROD_MODE_ENV`, uses production service hosts, and uses sync-service `-prod2`. |
| `MODE=staging` | Vite assets path | Uses `/staging` assets, but `servers.ts` treats it as non-development for host selection. |
| `VITE_*` | Vite runtime env | Automatically exposed by Vite and read by feature flag and server selectors. |
| `LOCAL_JWT` | `defineEnv()` | Injected as `import.meta.env.__LOCAL_JWT__` for local bearer-token auth paths. |
| `PORT` | Vite dev/preview server | Defaults to `3000`; used by `just local` and preview commands. |

Common commands:

```bash
cd js/app
bun run dev
```

```bash
cd js/app
MODE=production NODE_ENV=production bun run build
```

```bash
cd js/app
just build-dev
just build-staging
just build-prod
```

## Feature flag override rules

Feature flags are centralized in `packages/core/constant/featureFlags.ts`.

```ts
resolveFeatureFlag(flagName: string, defaultValue: boolean): boolean
```

For a flag named `ENABLE_EMAIL`, the runtime override key is `VITE_ENABLE_EMAIL`.

| Override value | Result |
| --- | --- |
| `true` | Forces the flag on. |
| `false` | Forces the flag off. |
| unset | Uses the code default. |
| any other value, such as `1` or `yes` | Ignored; uses the code default. |

<Note>
Feature flag values are string comparisons. Use `VITE_ENABLE_EMAIL=true`, not `VITE_ENABLE_EMAIL=1`.
</Note>

Example:

```bash
cd js/app/packages/app
VITE_ENABLE_BEARER_TOKEN_AUTH=true bun run --bun dev
```

The resolver behavior is covered by `packages/core/tests/featureFlags.test.ts`, including default fallback, `true`, `false`, and invalid values.

## Feature flag defaults

### Runtime mode flags

| Export | Value |
| --- | --- |
| `LOCAL_ONLY` | `true` when `import.meta.hot` is present, normally Vite dev with hot reload. |
| `DEV_MODE_ENV` | `true` when `import.meta.env.MODE === 'development'`. |
| `PROD_MODE_ENV` | `true` when `import.meta.env.MODE === 'production'`. |

### Boolean flags resolved through `VITE_<flag>`

| Export | Override key | Default |
| --- | --- | --- |
| `ENABLE_PDF_MODIFICATION_DATA_AUTOSAVE` | `VITE_ENABLE_PDF_MODIFICATION_DATA_AUTOSAVE` | `true` |
| `ENABLE_PDF_LOCATION_AUTOSAVE` | `VITE_ENABLE_PDF_LOCATION_AUTOSAVE` | `true` |
| `ENABLE_PDF_TABS` | `VITE_ENABLE_PDF_TABS` | `true` |
| `ENABLE_PDF_MARKUP` | `VITE_ENABLE_PDF_MARKUP` | `true` |
| `ENABLE_SCRIPTING` | `VITE_ENABLE_SCRIPTING` | `false` |
| `ENABLE_PDF_MULTISPLIT` | `VITE_ENABLE_PDF_MULTISPLIT` | `true` |
| `ENABLE_PROJECT_SHARING` | `VITE_ENABLE_PROJECT_SHARING` | `true` |
| `ENABLE_CANVAS_IMAGES` | `VITE_ENABLE_CANVAS_IMAGES` | `true` |
| `ENABLE_CANVAS_FILES` | `VITE_ENABLE_CANVAS_FILES` | `true` |
| `ENABLE_CANVAS_TEXT` | `VITE_ENABLE_CANVAS_TEXT` | `true` |
| `ENABLE_LIVE_INDICATORS` | `VITE_ENABLE_LIVE_INDICATORS` | `true` |
| `ENABLE_PROFILE_PICTURES` | `VITE_ENABLE_PROFILE_PICTURES` | `true` |
| `ENABLE_VIDEO_BLOCK` | `VITE_ENABLE_VIDEO_BLOCK` | `true` |
| `ENABLE_DOCX_TO_PDF` | `VITE_ENABLE_DOCX_TO_PDF` | `true` |
| `ENABLE_MARKDOWN_LIVE_COLLABORATION` | `VITE_ENABLE_MARKDOWN_LIVE_COLLABORATION` | `true` |
| `ENABLE_EMAIL` | `VITE_ENABLE_EMAIL` | `true` |
| `ENABLE_BLOCK_IN_BLOCK` | `VITE_ENABLE_BLOCK_IN_BLOCK` | `true` |
| `ENABLE_SEARCH_SERVICE` | `VITE_ENABLE_SEARCH_SERVICE` | `true` |
| `ENABLE_MARKDOWN_DIFF` | `VITE_ENABLE_MARKDOWN_DIFF` | `true` |
| `ENABLE_HISTORY_COMPONENT` | `VITE_ENABLE_HISTORY_COMPONENT` | `false` |
| `ENABLE_BEARER_TOKEN_AUTH` | `VITE_ENABLE_BEARER_TOKEN_AUTH` | `false` |
| `ENABLE_MARKDOWN_SEARCH_TEXT` | `VITE_ENABLE_MARKDOWN_SEARCH_TEXT` | `DEV_MODE_ENV` |
| `CANVAS_SVG_IMPORT` | `VITE_CANVAS_SVG_IMPORT` | `true` |
| `ENABLE_CANVAS_VIDEO` | `VITE_ENABLE_CANVAS_VIDEO` | `true` |
| `ENABLE_CANVAS_HEIC` | `VITE_ENABLE_CANVAS_HEIC` | `false` |
| `ENABLE_MARKDOWN_COMMENTS` | `VITE_ENABLE_MARKDOWN_COMMENTS` | `true` |
| `ENABLE_REFERENCES_MODAL` | `VITE_ENABLE_REFERENCES_MODAL` | `true` |
| `ENABLE_MENTION_TRACKING` | `VITE_ENABLE_MENTION_TRACKING` | `true` |
| `ENABLE_CHAT_CHANNEL_ATTACHMENT` | `VITE_ENABLE_CHAT_CHANNEL_ATTACHMENT` | `true` |
| `ENABLE_SVG_PREVIEW` | `VITE_ENABLE_SVG_PREVIEW` | `true` |
| `USE_WIDE_ICONS` | `VITE_USE_WIDE_ICONS` | `true` |
| `ENABLE_ANIMATED_ICONS` | `VITE_ENABLE_ANIMATED_ICONS` | `true` |
| `ENABLE_PREVIEW` | `VITE_ENABLE_PREVIEW` | `true` |
| `ENABLE_PROJECT_VIEW_PREVIEW` | `VITE_ENABLE_PROJECT_VIEW_PREVIEW` | `true` |
| `ENABLE_TTFT` | `VITE_ENABLE_TTFT` | `DEV_MODE_ENV` |
| `ENABLE_MULTI_INBOX` | `VITE_ENABLE_MULTI_INBOX` | `DEV_MODE_ENV` |
| `ENABLE_EMAIL_SHARING` | `VITE_ENABLE_EMAIL_SHARING` | `true` |
| `ENABLE_DOCUMENT_MENTION_NOTIFICATIONS` | `VITE_ENABLE_DOCUMENT_MENTION_NOTIFICATIONS` | `DEV_MODE_ENV` |
| `ENABLE_STATIC_DOCUMENT_CARDS` | `VITE_ENABLE_STATIC_DOCUMENT_CARDS` | `false` |
| `ENABLE_MARKDOWN_AI_GENERATE` | `VITE_ENABLE_MARKDOWN_AI_GENERATE` | `false` |
| `ENABLE_UNIFIED_LIST_AI_INPUT` | `VITE_ENABLE_UNIFIED_LIST_AI_INPUT` | `true` |
| `ENABLE_EMAIL_SCHEDULED_SEND` | `VITE_ENABLE_EMAIL_SCHEDULED_SEND` | `true` |
| `ENABLE_FEATURED_SEARCH_RESULTS` | `VITE_ENABLE_FEATURED_SEARCH_RESULTS` | `true` |
| `ENABLE_PROXY_EMAIL_IMAGES` | `VITE_ENABLE_PROXY_EMAIL_IMAGES` | `true` |
| `ENABLE_CLIENT_EMAIL_SIGNAL_FILTER` | `VITE_ENABLE_CLIENT_EMAIL_SIGNAL_FILTER` | `false` |
| `ENABLE_APP_STORE_QR_CODE` | `VITE_ENABLE_APP_STORE_QR_CODE` | `true` |
| `ENABLE_RAIL_CHAT_TASK_COMMENTS` | `VITE_RAIL_CHAT_TASK_COMMENTS` | `true` |
| `ENABLE_AUTO_UPDATE_UI` | `VITE_ENABLE_AUTO_UPDATE_UI` | `true` |
| `ENABLE_CALLKIT` | `VITE_ENABLE_CALLKIT` | `false` |
| `ENABLE_MARKDOWN_SIDE_PANEL` | `VITE_ENABLE_MARKDOWN_SIDE_PANEL` | `true` |
| `ENABLE_REFOCUS_HIGHLIGHT` | `VITE_ENABLE_REFOCUS_HIGHLIGHT` | `true` |
| `ENABLE_CREATE_PROPERTY` | `VITE_ENABLE_CREATE_PROPERTY` | `false` |

### PostHog and override-style flags

| Export | Behavior |
| --- | --- |
| `ENABLE_TEAMS_OVERRIDE` | `true` in development or when `VITE_ENABLE_TEAMS=true`; otherwise `undefined`. A `false` override becomes `undefined`, not exported `false`. |
| `ENABLE_CALLS()` | Returns `true` in development; otherwise falls back to PostHog flag `enable-calls`. |
| `ENABLE_NEW_ONBOARDING_OVERRIDE` | `true` in development; otherwise `undefined`. |
| `ENABLE_NEW_LOGIN_OVERRIDE` | `true` in development; otherwise `undefined`. |
| `ENABLE_INVITE_TEAM_ONBOARDING_OVERRIDE` | `true` in development; otherwise `undefined`. |
| `ENABLE_TEAM_INVITE_TIERS_OVERRIDE` | `true` in development; otherwise `undefined`. |
| `ENABLE_SOUP_GROUP_BY_OVERRIDE` | `true` in development; otherwise `undefined`. |
| `ENABLE_NEW_PRICING_OVERRIDE` | `true` in development or when `VITE_ENABLE_NEW_PRICING=true`; otherwise `undefined`. |

## Server host selection

`packages/core/constant/servers.ts` exports:

```ts
export const SERVER_HOSTS: Servers
export const SYNC_SERVICE_HOSTS
export const SYNC_PERMISSION_TOKEN_DSS_HOST
```

`SERVER_HOSTS` is remote by default. Local host selection is only evaluated when `import.meta.env.MODE === 'development'`.

| Mode | `VITE_LOCAL_SERVERS` | `SERVER_HOSTS` result |
| --- | --- | --- |
| non-development | any value | Remote production-style hosts. |
| development | unset or empty | Remote dev hosts. |
| development | `ALL` | All regular services use local hosts. |
| development | comma-separated service names | Only listed services use local hosts; all others remain remote dev hosts. |
| development | `service-name:port` | Listed service uses its local URL with only the port replaced. |

Examples:

```bash
cd js/app
just local
```

```bash
cd js/app
just servers='document-storage-service' local
```

```bash
cd js/app
just servers='document-storage-service:9001,email-service' port='3001' local
```

```bash
cd js/app/packages/app
VITE_LOCAL_SERVERS='document-storage-service:9001' bun run --bun dev
```

When a partial local selection is used, the selector logs entries like:

```text
Using local server document-storage-service: http://localhost:8086
```

<Warning>
Unknown service names throw at startup with `unknown server name <name>`. The current `just local-dcs` and `just local-search` recipes include names that are not present in `SERVER_HOSTS`; use `just local-services` or the table below before adding a partial selector.
</Warning>

## Regular service host reference

In development remote mode, most remote hosts use a `-dev` suffix. In non-development modes, the suffix is empty.

| Key | Local host | Remote host pattern |
| --- | --- | --- |
| `auth-service` | `http://localhost:8080` | `https://auth-service[-dev].macro.com` |
| `auth-logout` | `http://localhost:3000` | FusionAuth logout URL for dev or prod auth tenant |
| `pdf-service` | `http://localhost:4567` | `https://pdf-service[-dev].macro.com` |
| `document-storage-service` | `http://localhost:8086` | `https://cloud-storage[-dev].macro.com` |
| `websocket-service` | `ws://localhost:6969` | `wss://services[-dev].macro.com` |
| `cognition-service` | `http://localhost:8085` | `https://document-cognition[-dev].macro.com` |
| `connection-gateway` | `ws://localhost:8082` | `wss://connection-gateway[-dev].macro.com` |
| `notification-service` | `http://localhost:8089` | `https://notifications[-dev].macro.com` |
| `static-file` | `http://localhost:8100` | `https://static-file-service[-dev].macro.com` |
| `unfurl-service` | `http://localhost:8095` | `https://unfurl-service[-dev].macro.com` |
| `contacts` | `http://localhost:8083` | `https://contacts[-dev].macro.com` |
| `email-service` | `http://localhost:8087` | `https://email-service[-dev].macro.com` |
| `image-proxy-service` | `http://localhost:8097` | `https://image-proxy[-dev].macro.com` |
| `scheduled-action` | `http://localhost:8098` | `https://agent-schedule[-dev].macro.com` |

## Sync-service host selection

`SYNC_SERVICE_HOSTS` is separate from `SERVER_HOSTS`.

| Mode and selection | Worker URL | WebSocket URL |
| --- | --- | --- |
| non-development | `https://sync-service-prod2.macroverse.workers.dev` | `wss://sync-service-prod2.macroverse.workers.dev` |
| development, remote sync | `https://sync-service-dev3.macroverse.workers.dev` | `wss://sync-service-dev3.macroverse.workers.dev` |
| development, local sync | `http://localhost:8787` | `ws://localhost:8787` |

The sync selector chooses local sync when `VITE_LOCAL_SERVERS === 'ALL'` or when the string contains `sync-service`.

<Warning>
`sync-service` is not a regular `SERVER_HOSTS` key. Because `SERVER_HOSTS` validates every comma-separated entry before sync host selection completes, `VITE_LOCAL_SERVERS=sync-service` currently throws `unknown server name sync-service`. Use `VITE_LOCAL_SERVERS=ALL` for local sync-service in the current implementation, or update the regular server selector before relying on partial sync-only selection.
</Warning>

`SYNC_PERMISSION_TOKEN_DSS_HOST` follows the sync-service host:

| Sync-service selected | Permission token DSS host |
| --- | --- |
| Remote sync-service | Remote `document-storage-service`, so JWT secrets match the remote sync-service. |
| Local sync-service | Current `SERVER_HOSTS['document-storage-service']`, which may be local when `ALL` is selected. |

## Runtime host helpers

`servers.ts` also exposes static-file URL helpers:

```ts
staticFileIdEndpoint(id: string): string
staticFileSizedEndpoint(id: string, size: 'small' | 'medium'): string
staticFileSizedUrl(url: string, size: 'small' | 'medium'): string
```

| Helper | Output shape |
| --- | --- |
| `staticFileIdEndpoint('abc')` | `${SERVER_HOSTS['static-file']}/file/abc` |
| `staticFileSizedEndpoint('abc', 'small')` | `${SERVER_HOSTS['static-file']}/file/abc?size=320` |
| `staticFileSizedEndpoint('abc', 'medium')` | `${SERVER_HOSTS['static-file']}/file/abc?size=1080` |
| `staticFileSizedUrl(url, 'small')` | `${url}?size=320` |

`staticFileSizedUrl()` appends `?size=` directly. Pass a base URL without an existing query string.

## Verification

```bash
cd js/app
bunx --bun vitest packages/core/tests/featureFlags.test.ts
```

```bash
cd js/app
just local-services
```

```bash
cd js/app
VITE_LOCAL_SERVERS=ALL VITE_ENABLE_BEARER_TOKEN_AUTH=true LOCAL_JWT='<token>' bun run dev
```

Local E2E startup uses the same pattern: `VITE_LOCAL_SERVERS=ALL`, `VITE_ENABLE_BEARER_TOKEN_AUTH=true`, and `LOCAL_JWT` injected into Vite as `__LOCAL_JWT__`.

## Next

<CardGroup>
  <Card title="Running locally" href="/running-locally">
    Local backend stack setup, seed data, and local E2E smoke-test workflow.
  </Card>
  <Card title="Service clients" href="/service-clients">
    How frontend packages consume `SERVER_HOSTS` through typed service clients.
  </Card>
</CardGroup>
