Skip to content

Local Development Workflows

Local development in Wavemap is split between workspace package watchers, app runtimes, Docker-backed backend services, generated assets, and browser test runners. Use this page to choose the right local loop before reaching for deployed-dev commands.

Before the first dependency install, complete Private npm Access so pnpm and frontend Docker builds can resolve the private Codon UI CLI without placing a token in repository files.

This page is for developer-machine setup and local verification. Shared cloud environments, deployed runtime config, and operator recovery paths live in the operations docs.

LoopUse ForPrimary Commands
Docker-backed backend, local frontendNormal app development with local Postgres, seeded data, dependent packages watching for changes, and dev server Next.js.pnpm dev:docker:up, then pnpm -C apps/wavemap-front-end dev.
Non-Docker local watchersLocal services already exist and you want package/backend/frontend watchers in one terminal.pnpm dev.
Package developmentShared package contract or utility work.pnpm -F @wavemap/api-contracts dev, pnpm -F @wavemap/i18n dev, package tests.
Browser E2E local debuggingReal browser behavior against local runtimes.pnpm test:e2e:prepare, frontend dev, then front-end Playwright commands.
CI-style built frontendBuild/runtime parity for Playwright or production-like checks.Build i18n assets, pnpm -C apps/wavemap-front-end build, then start.
Pseudolocalization auditRendered-output proof that translated copy is transformed and naked JSX strings are detectable.Build with pseudolocalization env, then pnpm verify:i18n-pseudolocalization.
Docs site workStarlight content and navigation changes.pnpm -C apps/wavemap-docs dev, pnpm build:docs.

Prefer the Docker-backed backend plus local frontend for most product work. The front-end Docker compose path exists, but local next dev is the ordinary faster loop on macOS and Windows.

For the separate anonymous service, use Public API Foundation for pnpm dev:public-api, restricted local credentials and the disposable demo.

The app .env.example files document the local values expected by each runtime:

  • apps/wavemap-back-end/.env.example
  • apps/wavemap-front-end/.env.example
  • apps/wavemap-public-api/.env.example
  • packages/i18n/.env.example

Local .env.dev and .env.shared files are not tracked in git. Create local files from the example values and keep real secrets out of the repository.

Important local boundaries:

  • Frontend browser values use NEXT_PUBLIC_* and are visible to the browser.
  • Frontend server-only values do not use the NEXT_PUBLIC_* prefix.
  • Backend runtime values are consumed by the backend env parser and Docker Compose.
  • Shared i18n values exist in both frontend and backend-facing forms so the app and package agree on locale, namespace, and asset version behavior.
  • Pseudolocalization uses both public and non-public i18n env values when the browser and server runtime should agree on the selected replacement character set.
  • Local development values should not be copied into deployed-dev SSM, GitHub environments, or Pulumi config just because they have the same names.

Use Configuration And Secrets when adding, renaming, or moving an environment value.

The main local backend path is:

Terminal window
pnpm dev:docker:up

Before package work begins, the command confirms that the Docker daemon is available and that the resolved Compose topology parses. It then synchronously builds @wavemap/shared-utils, @wavemap/api-contracts, @wavemap/data-access, and @wavemap/i18n, including Wavemap’s required tsc-alias rewriting, before it starts the ownership-checked background watcher. If that initial build fails, Docker startup does not continue. Detailed watcher output is written to .wavemap/dev-docker/package-watcher.log.

The command then starts the root-owned backend development Compose project, resolves the active local media mode from the backend env file, and waits for database and database-connected API readiness. The backend service converges the local database before it launches the API, so readiness cannot predate pending migrations and an ordinary Compose or container restart follows the same convergence path. Compose owns the project’s ordinary network, so the backend reaches PostgreSQL through the wavemap_db service name without a separately created external network. A genuinely empty local database receives migrations, base fixtures, and development fixtures once. Later starts run migrations while preserving retained database and media state. An existing pre-marker Wavemap schema is adopted without reseeding; unknown non-empty schemas or database markers are refused.

Then run the frontend locally:

Terminal window
pnpm -C apps/wavemap-front-end dev

Direct frontend startup first builds the existing three-package runtime graph, including alias rewriting, and launches Next only after that build succeeds. The filtered form (pnpm -F wavemap-front-end dev) and root pnpm dev:frontend use the same path. Next arguments such as --port 3001 are forwarded. This preparation does not start another package watcher; use the Docker-backed or foreground package loop above when editing shared-package source. The combined pnpm dev command reuses its own initial build before launching Next.

Default local URLs:

  • Frontend: http://localhost:3000
  • Backend: http://localhost:6001
  • Readiness: http://localhost:6001/api/v1/ready
  • PostgreSQL: localhost:5434

These ports are loopback-only and do not conflict with Waveguide’s defaults. Override them when necessary with WAVEMAP_BACKEND_PORT and WAVEMAP_DATABASE_PORT.

Useful stack controls:

Terminal window
pnpm dev:docker:stop
pnpm dev:docker:reload
pnpm dev:docker:down
pnpm dev:docker:hard-reload
pnpm dev:packages:stop

Routine lifecycle commands preserve local data:

  • stop stops the backend/media containers and owned watcher while retaining containers and volumes.
  • down removes backend/media containers and the ordinary Compose project network, but retains named volumes.
  • reload keeps the owned watcher, database, and media state; it rebuilds and force-recreates only the backend service, which converges pending migrations before it becomes ready.
  • None of these commands parses or controls the legacy Dockerized frontend project. Run the frontend natively.

hard-reload is the coordinated destructive path for the selected backend/media Compose graph. It requires an exact confirmation before Docker inspection, verifies the exact wavemap_dev target and its repository-owned disposable marker, then removes only the resolved Wavemap development volumes and recreates the fixture baseline:

Terminal window
pnpm dev:docker:hard-reload -- --confirm=remove-wavemap-dev-volumes

For the narrower expert-only database reset, which intentionally leaves media state untouched, use:

Terminal window
pnpm -F wavemap-back-end db:reset-dev-db --confirm=reset-wavemap-dev-database-only

This path validates the exact confirmation before Docker work, then the backend reset command independently verifies the local-dev reset context, exact Compose database identity, and repository-owned disposable marker. Only then does it clear the application and Drizzle migration schemas and run migrations, base seed, and development seed against the empty database. Ordinary startup convergence and ordinary migrations remain data-preserving.

Use dev:packages:stop only when you need to stop the watcher without touching Docker. Use SKIP_DOCKER_WATCHERS=1 pnpm dev:docker:up when you intentionally do not want Docker startup to manage package watchers. This setting and CI=true still build the complete runtime package graph once before Compose starts; the host dist mounts need those outputs even when the image already contains built packages. The same build can be run independently with node bin/dev/watch-workspace-packages.mjs --build-only.

pgweb is available behind the root Compose tools profile and is not part of the normal graph:

Terminal window
docker compose -f compose.dev.yml --profile tools up --detach --wait pgweb

It binds to http://localhost:8081 by default and uses the same retained PostgreSQL service. Stop it explicitly with docker compose -f compose.dev.yml --profile tools stop pgweb when it is no longer needed.

dev:docker:up refuses package builds and watcher startup when the Docker daemon is unavailable. On macOS, start Docker Desktop and wait for the engine to be ready before retrying. The native Docker error and a short recovery hint remain visible in the terminal.

Compose build and startup output is streamed without filtering. If Compose fails, the lifecycle also prints current project status and the last 120 log lines. For further inspection:

Terminal window
docker compose -p wavemap_be_dev -f compose.dev.yml ps
docker compose -p wavemap_be_dev -f compose.dev.yml logs --tail=120
node bin/dev/manage-workspace-package-watcher.mjs status

The managed watcher log remains .wavemap/dev-docker/package-watcher.log.

The root command:

Terminal window
pnpm dev

uses the cross-platform local dev process manager. It runs these watcher/runtime processes in one terminal:

  • @wavemap/shared-utils
  • @wavemap/api-contracts
  • @wavemap/i18n
  • @wavemap/data-access
  • wavemap-back-end
  • wavemap-front-end

Use this loop when the required local services and env values already exist. The @wavemap/i18n watcher keeps package source exports flowing into dist for the app runtimes, while ordinary translation JSON edits are still read directly by local next dev through the rewrite-backed i18n asset routes.

Press Ctrl-C in the pnpm dev terminal to stop all child processes.

To run only the shared package watchers, use:

Terminal window
pnpm dev:packages

When pnpm dev:docker:up starts shared package watchers in the background, stop them with:

Terminal window
pnpm dev:packages:stop

Local media behavior is selected through backend env values:

  • MEDIA_STORAGE_PROVIDER
  • MEDIA_STORAGE_EXECUTION_TARGET
  • MEDIA_STORAGE_APPLICATION_ENVIRONMENT

Current local modes:

ModeEnv ShapeUse For
Mocked storageProvider: mocked
Target: mocked
App env: development or test.
Day-to-day app logic and tests that do not need object-store behavior.
S3 emulatorProvider: s3
Target: emulator
App env: development or test.
Local AWS-shaped upload/delete/path behavior through S3Mock.
Azure emulatorProvider: azure
Target: emulator
App env: development.
Azure continuity smoke through Azurite.
Real S3 validationProvider: s3
Target: cloud
App env: development.
Explicit real-cloud validation with a selected read-only AWS config directory.
Real Azure validationProvider: azure
Target: cloud
App env: development.
Explicit real-cloud validation with selected Azure connection values.

When MEDIA_STORAGE_APPLICATION_ENVIRONMENT is omitted, the backend falls back to NODE_ENV. When MEDIA_STORAGE_EXECUTION_TARGET is omitted, development and test resolve to emulator, while staging and production resolve to cloud.

Emulators are execution targets, not persisted providers. S3Mock and Azurite should not appear as stored media provider values.

pnpm dev:docker:up accepts only mocked/mocked, s3/emulator, azure/emulator, s3/cloud, or azure/cloud, and adds only the selected provider overlay. S3Mock retains its object filesystem and Azurite retains blob state in named volumes across routine lifecycle commands. The cloud overlays receive only their selected provider’s inputs; S3 cloud mode additionally requires WAVEMAP_AWS_CONFIG_DIR and mounts that directory read-only at /home/node/.aws.

The coordinated destructive command resets the database and the selected emulator volume together. To clear only the currently selected emulator while intentionally leaving database records untouched, use the expert-only exact-confirmed command:

Terminal window
pnpm -F wavemap-back-end media:reset-dev-emulator --confirm=reset-wavemap-dev-media-emulator-only

Real cloud modes are never targeted by either local emulator reset path.

Use Media Workflow And Validation for choosing the right media proof lane. Use Media Storage And Delivery for the durable media architecture boundary.

In local next dev, i18n asset requests are rewritten to API routes that read from Wavemap’s locale root or the foundation package’s locale root for package-owned common namespaces. Ordinary translation edits therefore do not require regenerating frontend public assets on every save.

Production-like frontend builds need a generated static snapshot:

Terminal window
pnpm -C packages/i18n build-and-validate:i18n-assets

Run that before frontend build/start flows when you need built-runtime parity:

Terminal window
pnpm -C packages/i18n build-and-validate:i18n-assets
pnpm -C apps/wavemap-front-end build
pnpm -C apps/wavemap-front-end start

Use i18n Assets And Delivery for the full asset and CDN boundary.

Use pseudolocalization when you want a browser-level signal for untranslated visible JSX text.

The fastest local proof uses the same command CI runs, but it expects a frontend runtime to already be serving:

Terminal window
pnpm verify:i18n-pseudolocalization -- --project=chromium

For built-runtime parity, generate assets and build the frontend with pseudolocalization active:

Terminal window
I18N_VERSION=0.1.0 pnpm -C packages/i18n build-and-validate:i18n-assets
NEXT_PUBLIC_AVAILABLE_LOCALES=en,fr \
NEXT_PUBLIC_DEFAULT_LOCALE=en \
NEXT_PUBLIC_I18N_VERSION=0.1.0 \
NEXT_PUBLIC_PSEUDOLOCALIZATION_ACTIVE=true \
NEXT_PUBLIC_PSEUDOLOCALIZATION_CHARACTER_SET=accented \
AVAILABLE_LOCALES=en,fr \
DEFAULT_LOCALE=en \
I18N_VERSION=0.1.0 \
PSEUDOLOCALIZATION_ACTIVE=true \
PSEUDOLOCALIZATION_CHARACTER_SET=accented \
pnpm -C apps/wavemap-front-end build

Start that built runtime in another shell, keeping the same pseudolocalization env values:

Terminal window
PLAYWRIGHT_FRONTEND_RUNTIME=build pnpm -C apps/wavemap-front-end start

Then run:

Terminal window
PLAYWRIGHT_FRONTEND_RUNTIME=build pnpm verify:i18n-pseudolocalization -- --project=chromium

The suite includes a deterministic probe route at /en/i18n-pseudolocalization-probe. That route intentionally renders one naked string so the scanner proves it can fail for the right reason. Do not treat that route as product UI.

Playwright does not start the app runtime for you. Start the backend and frontend first, then run browser tests.

Default local sequence:

Terminal window
pnpm dev:docker:up
pnpm test:e2e:prepare
pnpm -C apps/wavemap-front-end exec playwright install chromium

Start the frontend in its own shell:

Terminal window
pnpm -C apps/wavemap-front-end dev

Then run the smoke suite from another shell once http://localhost:3000 is serving:

Terminal window
pnpm -C apps/wavemap-front-end test:e2e:smoke

pnpm test:e2e:prepare builds the shared workspace packages whose package exports point at dist outputs. This keeps front-end Playwright imports aligned with package export behavior.

CI smoke uses a built frontend runtime instead of next dev so browser timing reflects built artifacts rather than on-demand route compilation:

Terminal window
pnpm -C packages/i18n build-and-validate:i18n-assets
pnpm -C apps/wavemap-front-end build

Start the built runtime in its own shell:

Terminal window
PLAYWRIGHT_FRONTEND_RUNTIME=build pnpm -C apps/wavemap-front-end start

Then run smoke against that built runtime from another shell:

Terminal window
pnpm -C apps/wavemap-front-end test:e2e:smoke -- --project=chromium

Use Testing for layer selection and browser-suite scope.

pnpm typecheck:packages refreshes the project-reference declarations and rewrites their internal aliases, but does not emit runtime JavaScript. Running it directly or through pnpm verify:tooling therefore preserves the runnable JavaScript used by an active frontend or backend. A typecheck is not a runtime build: use the package build scripts or the development startup commands to update executable output after source changes.

Useful local verification commands:

Terminal window
pnpm test:packages
pnpm test:unit
pnpm verify:i18n
pnpm verify:scripts
pnpm build:docs
pnpm -C apps/wavemap-docs dev

Prefer package-scoped commands when changing one package:

Terminal window
pnpm -F @wavemap/api-contracts test
pnpm -F @wavemap/i18n test
pnpm -F @wavemap/shared-utils test
pnpm -C apps/wavemap-front-end test:ci
pnpm -C apps/wavemap-back-end test:ci

Local development commands should not silently:

  • Run Pulumi.
  • Mutate deployed-dev runtime hosts.
  • Populate SSM parameters.
  • Push Docker images.
  • Publish docs to S3/CloudFront.
  • Reset deployed-dev data.
  • Use real cloud media storage without an explicit validation choice.

When the task crosses those boundaries, switch to the operations docs and move through the appropriate approval gates.