Skip to main content

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:

  1. The environment contract.
  2. The GET /meta endpoint the Chrome extension reads to theme itself.
  3. Where "pond" literals exist today and how to de-name them.
  4. The rename playbook when the real name is picked.

1. Environment contract

Naming / branding

VariablePurposeDefaultExample
APP_NAMEHuman-readable display nameAppRCP
APP_SLUGURL/identifier-safe versionapprcp
APP_TAGLINEOptional subtitle under the name(empty)Reading club for practitioners
APP_DESCRIPTIONLonger description for meta tags(empty)Curated content browser
APP_LOGO_URLLogo image (absolute or /-relative to API)(empty — client uses initial letter fallback)/assets/logo.svg
APP_ICON_URLSmall icon for favicon / extension action icon(empty)/assets/icon.png
APP_PRIMARY_COLORAccent for default theme#3b82f6#a855f7
APP_PUBLIC_URLCanonical public URL for deep links(empty)https://app.example.com
APP_VERSIONHuman version string surfaced in /metafrom package.json0.2.0

Infrastructure

VariablePurposeExample
PORTAPI listen port4100
NODE_ENVStandard runtime flagdevelopment / production
DB_NAMEMongo database name (replaces hardcoded pond)rcp
MONGO_ROOT_USERMongo auth user (docker-compose only)root
MONGO_ROOT_PASSWORDMongo auth password (docker-compose only)(secret)
MONGO_URIFull URI used by the APImongodb://root:${PW}@localhost:27018/${DB_NAME}?authSource=admin
ME_USER / ME_PASSWORDMongo Express basic auth

Auth

VariablePurposeNotes
JWT_ACCESS_SECRETAccess-token HMAC secret32+ random bytes
JWT_REFRESH_SECRETRefresh-token HMAC secret32+ random bytes, distinct from access
JWT_ACCESS_TTL_SECONDSAccess token lifetimedefault 900 (15 min)
JWT_REFRESH_TTL_SECONDSRefresh token lifetimedefault 2592000 (30 days)
AUTH_ALLOW_SELF_REGISTRATIONWhether /auth/register is opendefault true in dev, false in prod
CORS_ALLOWED_ORIGINSComma-separated origin listchrome-extension://abcd…,http://localhost:5173

Content

VariablePurpose
CONTENT_DEFAULT_GROUP_SLUGGroup used for submissions without an explicit group
PAGINATION_DEFAULT_LIMITOverride of default 30
PAGINATION_MAX_LIMITOverride of max 200
PAGINATION_MAX_OFFSETOverride 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-revalidate
  • ETag over 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):

FileOccurrenceAction
docker-compose.ymlService names pond-mongo, pond-mongo-express; network pond-net; volume pond-mongo-data; MONGO_INITDB_DATABASE: pondReplace with env interpolation (see §4).
api/.env.exampleMONGO_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:19console.log('Pond API listening on …')Replace with console.log('${APP_NAME ?? 'API'} listening on …') using the config.
docs/*Occasional mentions of "Pond" as project nameAcceptable 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:

  1. Set env values. In .env and api/.env, set APP_NAME, APP_SLUG, DB_NAME, APP_LOGO_URL, APP_PRIMARY_COLOR. No other changes for 95% of the rename.

  2. Rebuild containers. docker compose down && docker compose up -d — Mongo reads DB_NAME at init time. (An existing pond database remains; the new DB_NAME creates a fresh, empty DB. If you need to preserve data, run mongodump / mongorestore across the rename.)

  3. Rename package.json. One-line change: "name": "<slug>-api" and "description".

  4. Optional: rename the repo directory from Pond/ to the chosen short name. This is a mv plus updating local tooling paths (e.g. editor workspace files).

  5. Verify.

    • curl http://localhost:4100/meta returns the new branding.
    • The Chrome extension (on next cold start) picks up the new name and logo.
    • Docker containers use the new names (docker ps confirms).
  6. 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:

  1. On service-worker cold start, fetch GET /meta (or read the cached copy from chrome.storage.local; revalidate with ETag).
  2. Store { name, logoUrl, primaryColor, … } in a global ExtensionConfig.
  3. All UI surfaces (popup, side panel, new-tab page, options) read from ExtensionConfig for display.
  4. The extension's manifestmanifest.json name, 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.json ships 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.