Code Management
This page documents how code is managed in RoundTrip — from writing a commit to deploying to production. It covers branching strategy, commit conventions, pull request flow, ADO pipeline architecture, Cloudflare Pages deployment, and the self-hosted build agent.
Every developer on the team must follow these conventions. Deviating from them causes pipeline failures, merge conflicts, and in at least one case has overwritten the production frontend deployment.
Repositories
| Repo | Contents | ADO Path |
|---|---|---|
RoundTripAPI | .NET 10 API — all backend code | traxs/RoundTrip/RoundTripAPI |
RoundTripWeb | React 19 frontend — all frontend code | traxs/RoundTrip/RoundTripWeb |
roundtrip-internal | This ops site (Docusaurus) | traxs/RoundTrip/roundtrip-internal |
Both application repos follow identical branching and pipeline conventions.
Branch Strategy
main
│
│ ← Production deployments only
│ ← Protected: requires PR + approval
│ ← Auto-deploys to production on merge
│
development
│
│ ← Integration branch for all feature work
│ ← Auto-deploys to dev environment on merge
│ ← Feature branches merge here first
│
feature/TRA-NNN-description
fix/TRA-NNN-description
│
│ ← One branch per Linear ticket
│ ← Created from development
│ ← PR → development when complete
Branch Naming
| Type | Pattern | Example |
|---|---|---|
| Feature | feature/TRA-NNN-description | feature/TRA-280-free-text-parts |
| Bug fix | fix/TRA-NNN-description | fix/TRA-286-hold-reason-values |
| Chore | chore/description | chore/update-dependencies |
| Docs | docs/description | docs/update-infrastructure-guide |
Always create your branch from development — never from main:
git checkout development
git pull origin development
git checkout -B feature/TRA-NNN-description
Commit Conventions
RoundTrip uses Conventional Commits. Every commit message must follow this format:
type(scope): description
Examples:
feat(TRA-280): add free-text part logging on PWA
fix(TRA-286): correct hold reason enum values to match backend constraint
test(TRA-280): add unit tests for ad hoc part usage handler
chore: update Tailwind to v4.1
refactor(TRA-261): extract role from ITenantContext
| Type | When to Use |
|---|---|
feat | New feature or behaviour |
fix | Bug fix |
test | Tests added or updated |
chore | Dependencies, tooling, config — no behaviour change |
refactor | Internal restructure — no behaviour change |
docs | Documentation only |
One commit per Linear ticket. Do not squash multiple tickets into one commit or split one ticket across multiple commits.
Pull Request Flow
┌─────────────────────────┐
│ feature/TRA-NNN │
│ Write code + tests │
│ Commit per convention │
└────────────┬────────────┘
│ PR opened
▼
┌─────────────────────────┐
│ development │◄── Integration branch
│ │
│ CI Build runs: │
│ • dotnet build │
│ • dotnet test │
│ • npm run build │
│ │
│ On success: │
│ • Deploy to dev env │
│ • Playwright E2E │
└────────────┬────────────┘
│ PR opened (manual)
│ Pete approves
▼
┌─────────────────────────┐
│ main │◄── Production branch
│ │
│ CI Build runs: │
│ • dotnet build │
│ • dotnet test │
│ • npm run build │
│ │
│ On success: │
│ • Deploy to production │
└─────────────────────────┘
PR Rules
- PRs are merged sequentially — never merge multiple feature branches simultaneously. This causes merge conflicts and pipeline race conditions.
- Tests must pass before any PR is merged — do not merge a red build
- Tests must be written in the same session as the feature — do not open a PR without tests
- One PR per ticket — keep PRs small and focused
- PR template must be completed — both repos have
.azuredevops/pull_request_template.md
PR Template Checklist
The PR template covers:
- Linear ticket linked
- API handler pattern checklist (SignalR, CancellationToken, domain exceptions)
- Security checklist (no secrets committed, tenant scoping, endpoint roles)
- Database checklist (migrations handled, CHECK constraints)
- Frontend architecture checklist (API calls in
lib/api/, hooks infeatures/*/hooks/) - Tests written and passing
ADO Pipeline Architecture
Organization: traxs (Visual Studio Online)
Self-hosted agent: laptop in pool traxs-self-hosted
┌──────────────────────────────────────────────────────────────────┐
│ ADO PIPELINE OVERVIEW │
│ │
│ RoundTripAPI RoundTripWeb │
│ ───────────── ────────────── │
│ │
│ azure-pipelines.yml azure-pipelines.yml │
│ Trigger: push to main Trigger: push to main │
│ ┌─────────────────────┐ ┌──────────────────────────┐ │
│ │ 1. dotnet restore │ │ 1. npm ci │ │
│ │ 2. dotnet build │ │ 2. npm run build │ │
│ │ 3. dotnet test │ │ 3. wrangler pages deploy │ │
│ │ 4. dotnet publish │ │ --project roundtrip │ │
│ │ 5. Deploy to │ │ --branch main │ │
│ │ app-roundtrip │ └──────────────────────────┘ │
│ │ -production │ │
│ └─────────────────────┘ azure-pipelines-dev.yml │
│ Trigger: push to development │
│ azure-pipelines-dev.yml ┌──────────────────────────┐ │
│ Trigger: push to development │ 1. npm ci │ │
│ ┌─────────────────────┐ │ 2. npm run build │ │
│ │ 1. dotnet restore │ │ 3. wrangler pages deploy │ │
│ │ 2. dotnet build │ │ --project roundtrip │ │
│ │ 3. dotnet test │ │ -dev │ │
│ │ 4. dotnet publish │ │ --branch development │ ⚠ │
│ │ 5. Deploy to │ └──────────────────────────┘ │
│ │ app-roundtrip │ │
│ │ -dev │ azure-pipelines-internal.yml │
│ └─────────────────────┘ (ops.roundtrips.app) │
│ ┌──────────────────────────┐ │
│ │ 1. npm ci │ │
│ │ 2. npm run build │ │
│ │ 3. wrangler pages deploy │ │
│ │ --project roundtrip │ │
│ │ -internal │ │
│ │ --branch main │ │
│ └──────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Pipeline Variables
Both API pipelines require these ADO pipeline variables:
| Variable | Used For |
|---|---|
AZURE_WEBAPP_PUBLISH_PROFILE | App Service deployment credentials |
Both Web pipelines require:
| Variable | Used For |
|---|---|
CLOUDFLARE_API_TOKEN | Wrangler authentication |
CLOUDFLARE_ACCOUNT_ID | Wrangler target account |
Cloudflare Pages Deployment
:::danger Critical — Read Before Touching Pipeline Files
The dev and production Cloudflare Pages deployments are distinguished only by the --branch flag passed to Wrangler. Using the wrong branch flag in the wrong pipeline overwrites the wrong environment. This has happened and took production down. This is the root cause of TRA-195.
:::
Cloudflare Pages Projects:
roundtrip (PRODUCTION)
Production branch setting: main
Custom domain: roundtrips.app
Deployed by: azure-pipelines.yml in RoundTripWeb
Wrangler command: wrangler pages deploy build --project-name roundtrip --branch main
roundtrip-dev (DEVELOPMENT)
Production branch setting: development ← not "main"!
Custom domain: dev.roundtrips.app
Deployed by: azure-pipelines-dev.yml in RoundTripWeb
Wrangler command: wrangler pages deploy build --project-name roundtrip-dev --branch development
roundtrip-internal (OPS SITE)
Production branch setting: main
Custom domain: ops.roundtrips.app
Deployed by: azure-pipelines-internal.yml in roundtrip-internal
Protected by: Cloudflare Zero Trust Access (email OTP)
Cloudflare Pages Branch Mapping Rule
The roundtrip-dev project's production branch is set to development in the Cloudflare Pages project settings. This is intentional and must not be changed. It ensures that dev.roundtrips.app serves the development branch deployment.
CORRECT:
roundtrip-dev project → production branch: "development"
azure-pipelines-dev.yml → --branch development ✅
WRONG (causes production overwrite):
azure-pipelines-dev.yml → --branch main ❌
(deploys to roundtrip-dev project's "main" which bleeds into production)
If dev.roundtrips.app is serving stale content, check the Cloudflare Pages project settings branch mapping first before investigating the pipeline.
Self-Hosted Build Agent
All pipelines run on a self-hosted Azure DevOps agent named laptop in the traxs-self-hosted pool.
Why Self-Hosted?
- Microsoft-hosted agents have a 60-minute timeout that is insufficient for some builds
- Local .NET SDK and Node versions are guaranteed to match the development environment
- No per-minute billing for pipeline minutes
Agent Requirements
The agent machine must have installed:
| Tool | Required For |
|---|---|
| .NET 10 SDK | API build and test |
| Node.js 20.x | Frontend build |
| Wrangler CLI | Cloudflare Pages deployment |
| Azure CLI | App Service deployment |
| Docker | Integration tests (Testcontainers) |
Agent Health
If pipelines are failing with agent connectivity errors:
- Check the machine running the agent is on and not sleeping
- Open Azure DevOps → Organization Settings → Agent Pools →
traxs-self-hosted→ Agents - Verify
laptopshows as Online - If offline, open a terminal on the agent machine and restart the agent process
Target Pipeline Structure (Planned)
The current pipeline structure is functional but missing some automation. This is the target state:
Current state:
✅ Production API pipeline (auto-deploy on main merge)
✅ Production Web pipeline (auto-deploy on main merge)
✅ Playwright pipeline (manual trigger only)
⏳ Dev API pipeline (created, may need verification)
⏳ Dev Web pipeline (created, may need verification)
❌ Tests not running automatically in any pipeline
❌ No ADO approval gate on production deployment
❌ Playwright not integrated into CI
Target state:
✅ Production API pipeline
✅ Production Web pipeline
✅ Dev API pipeline (auto-deploy on development merge)
✅ Dev Web pipeline (auto-deploy on development merge)
✅ Unit + integration tests run on every PR
✅ Playwright E2E runs against dev after dev deployment
✅ ADO approval gate required before production deployment
✅ Failed tests block merge
Tickets to create in Linear:
- Add unit + integration test step to API pipelines
- Add approval gate to production deployment
- Integrate Playwright into dev pipeline post-deployment
Linear Issue Tracking
All work — features, bugs, chores, documentation — is tracked as tickets in Linear using the TRA-NNN numbering series.
| Convention | Detail |
|---|---|
| Every branch references a ticket | feature/TRA-280-description |
| Every commit references a ticket | feat(TRA-280): description |
| Ticket updated before closing | Status, comments, decisions documented |
| Tests written before closing | Never mark Done without tests |
| Architecture decisions documented | Add a comment to the ticket with the decision and rationale |
Making a Change — End to End
Here is the complete workflow for a typical feature ticket from start to merge:
# 1. Start from development
git checkout development
git pull origin development
# 2. Create feature branch
git checkout -B feature/TRA-280-free-text-parts
# 3. Write code + tests
# ... development work ...
# 4. Run tests locally before committing
dotnet test --filter "Category!=Integration"
# 5. Commit
git add .
git commit -m "feat(TRA-280): add free-text part logging on PWA"
git commit -m "test(TRA-280): add handler and domain tests for ad hoc part usage"
# 6. Push and open PR → development
git push -u origin feature/TRA-280-free-text-parts
# Open PR in ADO: feature/TRA-280-free-text-parts → development
# 7. CI builds and deploys to dev
# Verify on dev.roundtrips.app
# 8. Open PR: development → main
# Complete PR template checklist
# Pete approves
# 9. CI builds and deploys to production
# Verify on roundtrips.app
# 10. Mark ticket Done in Linear
Related Pages
- Deployment Architecture — Azure resource inventory and environment details
- Request Pipeline — what happens after the code is deployed
- Infrastructure Reference — resource names, Key Vault, runbooks
- Testing — test suite overview and running tests locally