Configuration & Branding
This project is currently codenamed Pond. The name is temporary and will change (probably to RCP or another short name). To make rebranding a configuration change rather than a code change, the project name, logo, and other display-level branding are driven by environment variables and exposed at runtime via GET /meta.
This document covers:
- The environment contract.
- The
GET /metaendpoint the Chrome extension reads to theme itself. - Where "pond" literals exist today and how to de-name them.
- The rename playbook when the real name is picked.
1. Environment contract
Naming / branding
| Variable | Purpose | Default | Example |
|---|---|---|---|
APP_NAME | Human-readable display name | App | RCP |
APP_SLUG | URL/identifier-safe version | app | rcp |
APP_TAGLINE | Optional subtitle under the name | (empty) | Reading club for practitioners |
APP_DESCRIPTION | Longer description for meta tags | (empty) | Curated content browser |
APP_LOGO_URL | Logo image (absolute or /-relative to API) | (empty — client uses initial letter fallback) | /assets/logo.svg |
APP_ICON_URL | Small icon for favicon / extension action icon | (empty) | /assets/icon.png |
APP_PRIMARY_COLOR | Accent for default theme | #3b82f6 | #a855f7 |
APP_PUBLIC_URL | Canonical public URL for deep links | (empty) | https://app.example.com |
APP_VERSION | Human version string surfaced in /meta | from package.json | 0.2.0 |
Infrastructure
| Variable | Purpose | Example |
|---|---|---|
PORT | API listen port | 4100 |
NODE_ENV | Standard runtime flag | development / production |
DB_NAME | Mongo database name (replaces hardcoded pond) | rcp |
MONGO_ROOT_USER | Mongo auth user (docker-compose only) | root |
MONGO_ROOT_PASSWORD | Mongo auth password (docker-compose only) | (secret) |
MONGO_URI | Full URI used by the API | mongodb://root:${PW}@localhost:27018/${DB_NAME}?authSource=admin |
ME_USER / ME_PASSWORD | Mongo Express basic auth | — |
Auth
| Variable | Purpose | Notes |
|---|---|---|
JWT_ACCESS_SECRET | Access-token HMAC secret | 32+ random bytes |
JWT_REFRESH_SECRET | Refresh-token HMAC secret | 32+ random bytes, distinct from access |
JWT_ACCESS_TTL_SECONDS | Access token lifetime | default 900 (15 min) |
JWT_REFRESH_TTL_SECONDS | Refresh token lifetime | default 2592000 (30 days) |
AUTH_ALLOW_SELF_REGISTRATION | Whether /auth/register is open | default true in dev, false in prod |
CORS_ALLOWED_ORIGINS | Comma-separated origin list | chrome-extension://abcd…,http://localhost:5173 |
Content
| Variable | Purpose |
|---|---|
CONTENT_DEFAULT_GROUP_SLUG | Group used for submissions without an explicit group |
PAGINATION_DEFAULT_LIMIT | Override of default 30 |
PAGINATION_MAX_LIMIT | Override of max 200 |
PAGINATION_MAX_OFFSET | Override of max 10000 |
Each variable is read via ConfigService with a typed accessor — never process.env.X scattered through the codebase.
2. GET /meta
Public, cached, cheap. The Chrome extension fetches it on cold start and uses the values to render its header, title, colors, and logo. Without this endpoint, renaming requires shipping a new extension build.
Response
{
"success": true,
"data": {
"name": "RCP",
"slug": "rcp",
"tagline": "Reading club for practitioners",
"description": "Curated content browser",
"logoUrl": "https://api.example.com/assets/logo.svg",
"iconUrl": "https://api.example.com/assets/icon.png",
"primaryColor": "#a855f7",
"publicUrl": "https://app.example.com",
"version": "0.2.0",
"features": {
"selfRegistration": false,
"submissionsOpen": true
}
}
}
Caching
Cache-Control: public, max-age=300, must-revalidateETagover the serialized payload.- Extension revalidates on cold start using
If-None-Match; 304 is the common case.
Why include features
The client should branch on capability, not on version strings. features.selfRegistration controls whether the extension shows a "Sign up" link. features.submissionsOpen controls the toolbar submit action. Add feature flags as they emerge; prefer boolean capabilities over version comparisons.
Never put secrets here
/meta is anonymous and aggressively cached. Never include per-user data, per-deployment secrets, or anything that could vary by caller. If you find yourself wanting to, split into /meta (public, cached) and /auth/me (per-user, uncached).
3. Where "pond" literals exist today
Inventory (verified by reading the repo):
| File | Occurrence | Action |
|---|---|---|
docker-compose.yml | Service names pond-mongo, pond-mongo-express; network pond-net; volume pond-mongo-data; MONGO_INITDB_DATABASE: pond | Replace with env interpolation (see §4). |
api/.env.example | MONGO_URI=mongodb://…/pond?… | Replace DB name with ${DB_NAME} and add DB_NAME=app to example. |
api/package.json | "name": "pond-api", "description": "Pond sandbox API: …" | Rename to "api" and generic description, or leave until the real name is picked (see §4). |
api/src/main.ts:19 | console.log('Pond API listening on …') | Replace with console.log('${APP_NAME ?? 'API'} listening on …') using the config. |
docs/* | Occasional mentions of "Pond" as project name | Acceptable in rename-explanation contexts (this doc); avoid elsewhere. |
No code uses pond as a variable identifier. Modules are UsersModule, RolesModule, etc. Future modules should follow this convention (ContentModule, TagModule, CatalogModule) — never PondContentModule.
4. Rename playbook
When the real name is chosen:
-
Set env values. In
.envandapi/.env, setAPP_NAME,APP_SLUG,DB_NAME,APP_LOGO_URL,APP_PRIMARY_COLOR. No other changes for 95% of the rename. -
Rebuild containers.
docker compose down && docker compose up -d— Mongo readsDB_NAMEat init time. (An existingponddatabase remains; the newDB_NAMEcreates a fresh, empty DB. If you need to preserve data, runmongodump/mongorestoreacross the rename.) -
Rename
package.json. One-line change:"name": "<slug>-api"and"description". -
Optional: rename the repo directory from
Pond/to the chosen short name. This is amvplus updating local tooling paths (e.g. editor workspace files). -
Verify.
curl http://localhost:4100/metareturns the new branding.- The Chrome extension (on next cold start) picks up the new name and logo.
- Docker containers use the new names (
docker psconfirms).
-
Deferred: migrate old Mongo database if there is data to preserve. Pre-launch, there isn't — skip.
Proposed docker-compose edits
Before:
services:
pond-mongo:
container_name: pond-mongo
environment:
MONGO_INITDB_DATABASE: pond
volumes:
- pond-mongo-data:/data/db
networks:
- pond-net
After:
services:
mongo:
container_name: ${APP_SLUG:-app}-mongo
environment:
MONGO_INITDB_DATABASE: ${DB_NAME:-app}
volumes:
- mongo-data:/data/db
networks:
- app-net
The service key (mongo vs pond-mongo) is what other services reference internally, so keep it short and generic. The container_name is what shows up in docker ps, so that one gets the prettified ${APP_SLUG}-mongo.
Proposed .env.example (top-level, for docker-compose)
# Branding
APP_NAME=App
APP_SLUG=app
# Mongo (docker-compose)
MONGO_ROOT_USER=root
MONGO_ROOT_PASSWORD=change-me
DB_NAME=app
# Mongo Express
ME_USER=admin
ME_PASSWORD=change-me
Proposed api/.env.example
# Runtime
PORT=4100
NODE_ENV=development
# Branding (surfaced at GET /meta)
APP_NAME=App
APP_SLUG=app
APP_TAGLINE=
APP_LOGO_URL=
APP_ICON_URL=
APP_PRIMARY_COLOR=#3b82f6
# Database
DB_NAME=app
MONGO_URI=mongodb://root:change-me@localhost:27018/app?authSource=admin
# Auth
JWT_ACCESS_SECRET=replace-me-with-32-random-bytes
JWT_REFRESH_SECRET=replace-me-with-a-DIFFERENT-32-bytes
JWT_ACCESS_TTL_SECONDS=900
JWT_REFRESH_TTL_SECONDS=2592000
AUTH_ALLOW_SELF_REGISTRATION=true
# CORS
CORS_ALLOWED_ORIGINS=chrome-extension://__dev_id__
Note that MONGO_URI still hardcodes /app in the example — the Nest config can instead build it from parts at bootstrap (mongodb://${USER}:${PW}@${HOST}:${PORT}/${DB_NAME}), which avoids keeping the DB name in two places. Pick one pattern and stay with it.
5. Client-side branding
The Chrome extension never ships strings like "Pond" or any specific project name. Instead:
- On service-worker cold start, fetch
GET /meta(or read the cached copy fromchrome.storage.local; revalidate withETag). - Store
{ name, logoUrl, primaryColor, … }in a global ExtensionConfig. - All UI surfaces (popup, side panel, new-tab page, options) read from ExtensionConfig for display.
- The extension's manifest —
manifest.jsonname,short_name,description, icon paths — is the one place where the name is static. These are read by Chrome before the service worker starts and cannot be dynamic. Accept the tradeoff:manifest.jsonships with the chosen name; a rename involves a new Chrome Web Store submission. The rest of the UI updates live via/meta.
Plan the Chrome Web Store submission after the real name is locked in.