Skip to content

syncc — Universal Agent Brief

Tool-agnostic project guidelines for any coding agent (Codex, Copilot, Cursor, Claude, etc.). For Claude-specific instructions, see CLAUDE.md. For app-specific details, see apps/<app>/CONTEXT.md.


Project

syncc (always lowercase, two C's) — digitalization of public transportation for Bolivian municipalities. B2G SaaS targeting municipalities, transport syndicates, and government regulators.

Monorepo Structure

apps/
├── api-server/        NestJS 11 + Prisma 6 + PostgreSQL/PostGIS + Redis
├── web-admin/         Angular 20 admin dashboard (Tailwind, Leaflet)
├── mobile-driver/     Ionic 8 / Angular 20 driver app (Capacitor)
├── passengers-app/    Ionic 8 / Angular 20 passenger app (Capacitor)
├── syncc-landing/     Angular 20 public landing page (Tailwind, dark mode)
├── analytics/         Angular 20 internal analytics dashboard (tsparticles login)
└── qa-portal/         Angular 20 internal QA portal — test case management

Each app is self-contained with its own package.json, build config, and Dockerfile. No shared libs/ directory. Each app has a CONTEXT.md with routes, services, and app-specific instructions.

Internal Tools (analytics, qa-portal)

analytics and qa-portal are internal-only apps — never referenced from web-admin or the mobile apps: - analytics: monitors platform-wide events, device telemetry, and silo health. Authenticated via the same JWT/roles system. Uses ngx-particles (tsparticles) on the login page. - qa-portal: manages test cases, manual execution logs, and QA coverage. Roles: QA, ADMIN, SUPER_ADMIN, SUPER_ADMIN_LOCAL. QA users are created exclusively inside the portal (never from web-admin). Test case codes follow the format TC-{APP}-{NNN} (e.g. TC-DR-001).

Coding Conventions

Angular (all frontend apps)

  • Angular 20, standalone components only — no @NgModule anywhere
  • Signals: signal(), computed(), effect() — no BehaviorSubject for local state
  • Data fetching: resource() API — no manual subscribe/unsubscribe
  • Route params: input.required<string>() — no ActivatedRoute.snapshot
  • Templates: @if, @for, @switch — never *ngIf, *ngFor, ngSwitch
  • DI: inject() function — no constructor parameter injection
  • File naming: kebab-case.component.ts, kebab-case.service.ts, selector prefix app-

NestJS (api-server)

  • Prisma 6 for all DB access — no raw SQL except $queryRaw for PostGIS
  • TypeScript only — no .js files in src/
  • Feature modules follow: <feature>.module.ts, <feature>.controller.ts, <feature>.service.ts, dto/

Styling

  • Tailwind CSS per-app (tailwind.config.js in each app) — no Sass/SCSS, no inline style=""
  • Font: Inter via Google Fonts <link> tag — never via npm

Branding (Canonical Colors)

Primary:  #32737E (DEFAULT)  |  #E8F2F3 (light)  |  #245960 (dark)
Surface:  #FFFFFF            |  Secondary: #000000
Semantic: emerald-500 (success), orange-400 (warning), red-600 (error), blue-500 (info)

Spacing: rounded-2xl cards, rounded-xl buttons/inputs, p-5/p-6 card padding, space-y-6 sections.

Architecture Rules

GIS

  • Line.polyline (String) = Google Encoded Polyline — visual geometry only
  • Checkpoint.location = PostGIS geography(Point,4326) — proximity via ST_DWithin
  • Route.points does NOT exist — geometry lives on Line.polyline only
  • OSRM called from the browser/device — never proxied through the backend
  • Every polyline on a map MUST follow real road geometry — no straight lines through buildings

Auth

  • JWT strategy returns { userId, email, role, tenantId } — driver services look up by userId
  • OTP dev bypass (OTP_MASTER_KEY=000000) fires before driver status guard
  • ConfigModule loads .env.${NODE_ENV} first, then .env as fallback

Data Integrity

  • Trip.actualCheckpoints (Json) is a historical log — never remove or reset
  • QR / check-in / payment UI is hidden (not deleted) — restore instructions in mobile-driver and passengers-app CONTEXT.md

E2E Testing Strategy

Tool: Playwright

Core Principle: Tests MUST use the actual codebase algorithms and functions. The goal is to validate real implementation, not simplified mocks.

What to Test

  • Line creation flow: polyline drawing → OSRM road snapping → encoding/decoding
  • Route creation: checkpoint placement → snapping to polyline → distance calculations
  • Driver GPS flow: location updates → proximity checks → checkpoint clearance
  • Passenger flow: line search → route display → ETA calculations
  • Real-time monitoring: GPS updates → map rendering → polyline decoding

Critical Rules

  1. Use actual algorithms — import and call decodePolyline(), encodePolyline(), snapToPolyline(), distance calculation functions, ETA logic from the codebase
  2. Use actual APIs — call OSRM (router.project-osrm.org), backend endpoints (/lines, /routes, /location/update)
  3. NO simplified mocks — do not draw straight lines manually, do not hardcode distances, do not bypass real logic
  4. If a test fails, fix the code — never adapt the test to make it pass unless the test itself is wrong
  5. Validate end-to-end — tests should exercise the full flow from user action → backend → database → response

Definition of Done

A feature/flow is NOT complete until its E2E test passes.

  • After coding a new flow → create E2E test → run test → feature is done when test passes
  • When modifying an algorithm, business logic, or flow → update corresponding E2E tests → verify tests still pass
  • No feature ships without E2E test coverage

This ensures quality and prevents regressions. Tests are not optional—they are part of the implementation.

Example Anti-Patterns (DO NOT DO THIS)

// ❌ WRONG: Drawing straight lines instead of using OSRM
const straightLine = [[lat1, lng1], [lat2, lng2]];

// ✅ CORRECT: Use actual OSRM routing + polyline encoding
const route = await fetch(`router.project-osrm.org/route/v1/driving/${lng1},${lat1};${lng2},${lat2}?geometries=polyline`);
const polyline = encodePolyline(route.geometry.coordinates);
// ❌ WRONG: Hardcoding distance calculation
const distance = 1000; // meters

// ✅ CORRECT: Use actual distance calculation from codebase
const distance = calculateDistance(checkpoint1.location, checkpoint2.location);
// ❌ WRONG: Adapting test expectations to match wrong output
expect(eta).toBe(5); // changed from 10 to make test pass

// ✅ CORRECT: If ETA is wrong, fix the ETA calculation logic, not the test

Test Organization

  • apps/web-admin/e2e/tests/ — admin flows (line/route creation)
  • apps/mobile-driver/e2e/tests/ — driver flows (GPS, trip management)
  • apps/passengers-app/e2e/tests/ — passenger flows (search, booking)
  • Shared helpers in e2e/helpers/ — but helpers MUST import actual codebase functions

Seeding Strategy

  • Use apps/api-server/prisma/seed-e2e.ts for consistent test data
  • Seed real coordinates, real polylines (not straight lines)
  • Seed complete entity graphs (Tenant → Line → Route → Checkpoints)

Docker & Build

  • Two-stage Dockerfile: node:22-alpine build → nginx:stable-alpine serve
  • npm ci --legacy-peer-deps required (Leaflet/Angular peer dep conflicts)
  • Build output: dist/<app>/browser/ (Angular 17+ esbuild nested subfolder)
  • nginx: SPA fallback (try_files), gzip, no-cache on index.html, 1y immutable on assets
docker compose --env-file apps/api-server/.env.development up --build

Security

  • Never commit .env files — only .env.example in version control
  • Never log JWTs, OTP codes, or passwords
  • Avoid any type without explicit justification
  • Never disable CSP or CORS globally
  • All DB queries through Prisma — no string interpolation in $queryRaw

Knowledge Base & Wiki

Location: GitHub Wiki on the syncc repository

The Wiki contains the project's Knowledge Base, including all algorithms, calculations, technical documentation, architectural decisions, and conventions.

When to Consult the Wiki

If you lack context or have doubts about an algorithm, calculation, or architectural pattern — consult the Wiki first.

You don't need to check the Wiki before every change, but use it when: - You need to understand how an existing algorithm works - You're unsure about a calculation formula or business logic - You need context on an architectural decision - You're implementing something similar to existing functionality

The Wiki is the single source of truth for domain knowledge that isn't obvious from the code itself.

Critical Rule: Keep Wiki in Sync

When you create or modify an algorithm, you MUST update the Wiki immediately.

Examples of algorithm changes that require Wiki updates: - Polyline encoding/decoding logic - Distance calculation formulas - ETA calculation algorithms - Proximity detection logic - Route optimization algorithms - Checkpoint snapping algorithms - GPS accuracy filtering - Fare calculation formulas

How to Update

  1. When implementing/modifying an algorithm, document it in the Wiki
  2. Include: purpose, inputs, outputs, formula/logic, edge cases, examples
  3. Keep Wiki page structure organized by domain (GIS, Routing, Pricing, etc.)
  4. Cross-reference Wiki pages from code comments where applicable

The Wiki is the single source of truth for algorithm documentation. Keeping it current ensures knowledge continuity and helps onboard new developers.

Quick Reference

Need Where to look
Backend schema, auth, modules apps/api-server/CONTEXT.md
Admin routes, components apps/web-admin/CONTEXT.md
Driver routes, GPS flow apps/mobile-driver/CONTEXT.md
Passenger routes, services apps/passengers-app/CONTEXT.md
Landing dark mode, assets apps/syncc-landing/CONTEXT.md
Analytics routes, silos, events apps/analytics/CONTEXT.md
QA portal routes, test case codes, execution API apps/qa-portal/CONTEXT.md

Last updated: 2026-07-07.