Skip to main content

Coding Standards

Coding StandardsStandards: Drop — Fintech Payment App

Project: {{PROJECT_NAME}}Drop — Remittance + QR Payments Version: {{VERSION}}1.0 Date: {{DATE}}2026-02-23 Author: {{AUTHOR}}John (AI Director) Status: Draft | In Review | Approved Reviewers: {{REVIEWERS}}Alem Bašić (CEO)

Document History

Version Date Author Changes
0.1 {{DATE}}2026-02-23 {{AUTHOR}}John Initial draftstandards — TypeScript strict, Next.js App Router, fintech security rules

Purpose

These standards apply to all code in {{PROJECT_NAME}}Drop. When automated tools enforce a rule, the tool wins. When in doubt, optimize for readability — the next personAI agent reading your code mayis betrying youto inunderstand sixintent, months.not guess it.

Non-negotiable: All new code must pass automatedTypeScript lintingstrict check, ESLint, and Prettier formatting checks before merge. No exceptions.

Fintech non-negotiable: Parameterized SQL everywhere. No stored balances. No stored card numbers or CVV. These are encoded as automated tests — failing these means P0.


1. Language-Specific Conventions

1.1 Naming Conventions

JavaScriptTypeScript /(Drop's TypeScript:primary language):

Type Convention Example
Variables camelCase userNameuserId, isLoadingkycStatus
Functions camelCase getUserById(hashPassword(), validateEmail(calculateFee()
Classes PascalCase UserService, PaymentProcessorTransactionService
Interfaces PascalCase (no I prefix) User, PaymentResultTransactionResult
Types PascalCase UserIdKycStatus, ApiResponse<T>CurrencyCode
Enums PascalCase UserRoleTransactionType, PaymentStatusKycStatus
Constants UPPER_SNAKE_CASE MAX_RETRY_COUNTREMITTANCE_FEE_RATE, API_BASE_URLMIN_AMOUNT_NOK
Files — components PascalCase.tsx UserProfile.SendMoneyForm.tsx
Files — utilities camelCase.ts formatDate.calculateFee.ts, useAuth.verifyJWT.ts
Files — API routesroute.ts (Next.js convention)app/api/auth/login/route.ts
Test files{name}.test.tsauth.test.ts, db.test.ts
E2E test files{name}.spec.tsuser-flows.spec.ts

Python:Drop-specific constants:

// 
Fee rates (business rules change requires CEO approval 0.5% const
TypeConventionExample
Variables+ ADR) const REMITTANCE_FEE_RATE = 0.005; // functionssnake_caseuser_nameQR_MERCHANT_FEE_RATE = 0.01; // 1% const MIN_REMITTANCE_NOK = 100; const MAX_REMITTANCE_NOK = 50_000; // Supported corridors const SUPPORTED_CURRENCIES = ['RSD', get_user_by_id()'BAM', 'PKR', 'TRY', 'PLN', 'EUR'] as const; type CurrencyCode = typeof SUPPORTED_CURRENCIES[number];
ClassesPascalCaseUserService
ConstantsUPPER_SNAKE_CASEMAX_RETRY_COUNT
Modules / filessnake_caseuser_service.py
Private_single_leading_underscore_internal_helper()

1.2 File Organization

src/drop-app/src/
├── {{MODULE_1}}/app/
│   ├── index.tsapi/                  # PublicNext.js exportsAPI onlyRoutes
│   │   ├── {{MODULE_1}}.service.tsauth/
#   Business logic   │   ├── {{MODULE_1}}.controller.login/route.ts
#   HTTP handlers   │   ├── {{MODULE_1}}.types.register/route.ts
#   Types/interfaces│   │   └── me/route.ts
│   │   ├── {{MODULE_1}}.test.transactions/
│   │   │   ├── remittance/route.ts
#   Unit tests   │   └── {{MODULE_1}}.integration.test.qr-payment/route.ts
│   │   └── shared/rates/
# Shared utilities, no business logic   │       ├── errors/route.ts           # GET /api/rates
│   │       └── [currency]/route.ts # GET /api/rates/RSD
│   └── (app)/               # Protected pages (dashboard, send-money, etc.)
├── lib/
│   ├── utils/auth.ts              # JWT, bcrypt, session helpers
│   ├── db.ts                # SQLite/PostgreSQL client
│   ├── validate.ts          # Zod schemas for input validation
│   └── types/fee.ts               # Fee calculation functions
└── config/components/
    ├── drop-logo.tsx        # ConfigurationBrand loadinglogo component
    └── ui/                  # Shared UI components

Rules:

  • One class/componentroute handler per file (route.ts in Next.js App Router)
  • FileBusiness namelogic matchesextracted theto primarylib/ export namenever inline in route handlers
  • Test files co-located with source files (not in a separate /test__tests__/ directory)
  • index.ts files only for re-exporting — no logic

1.3 Import Ordering

// 1. Node built-ins
import { readFileSynccookies } from 'fs';
import path from 'path'next/headers';

// 2. External dependencies (node_modules)
import express from 'express';
import { z } from 'zod';
import * as bcrypt from 'bcrypt';
import { SignJWT, jwtVerify } from 'jose';

// 3. Internal — absolute paths (@/ alias)
import { UserServicedb } from '@/services/user.service'lib/db';
import { loggerverifyJWT } from '@/shared/logger'lib/auth';
import type { User } from '@/lib/types';

// 4. Internal — relative paths
import { validateRequest } from '../middleware/validate';
import type { CreateUserDtovalidateRemittanceBody } from './user.types'validate';

Enforced by: ESLint eslint-plugin-importimport/order / isort (Python)rule

1.4 Error Handling Patterns

Pattern: Return typed errors, never throw in business logic unless truly exceptional.

// PREFERRED — typedconsistent resultHTTP patternerror typeresponses Result<T,in ENext.js =API Error>routes
= { ok: true; value: T } | { ok: false; error: E };export async function createUser(dto:POST(request: CreateUserDto)Request): Promise<Result<User, ValidationError | DatabaseError>Response> {
  let body: unknown;
  try {
    body = await request.json();
  } catch {
    return Response.json({ error: 'Invalid JSON body' }, { status: 400 });
  }

  const validation = validateUser(dto)remittanceSchema.safeParse(body);
  if (!validation.ok)success) {
    return Response.json(
      { ok: false, error: new'Validation ValidationError(failed', details: validation.message)error.flatten() },
      { status: 422 }
    );
  // ...
  }

  // ACCEPTABLEBusiness logic in lib/throwthrows only at application boundaries (HTTP handlers)
// FORBIDDEN — swallowtyped errors silently
  try {
    doSomething(const result = await processRemittance(validation.data);
    return Response.json(result, { status: 201 });
  } catch (err)error) {
    if (error instanceof InsufficientBalanceError) {
      return Response.json({ error: 'Insufficient balance' }, { status: 402 });
    }
    if (error instanceof KycRequiredError) {
      return Response.json({ error: 'KYC verification required' }, { status: 403 });
    }
    // Never writeexpose thisinternal withouterrors
    handling theconsole.error('Unhandled error in remittance:', error);
    return Response.json({ error: 'Internal server error' }, { status: 500 });
  }
}

Async/await:Rules:

Always
  • NEVER return stack traces or DB error messages to the client
  • NEVER use try/catchany type — TypeScript strict mode enforces this
  • NEVER swallow errors silently
  • Use 402 for Insufficient Balance (fintech convention), not 400
  • Error messages in asyncNorwegian functionsfor atuser-facing thevalidation
  • controller layer. Promise chaining: Avoid — prefer async/await. Logging: Log errors with error level at the boundary. Do NOT re-log in nested functions.


2. Code Formatting

2.1 Formatter

Language Tool Config File Enforced In
TypeScript / JavaScript {{FORMATTER}} Prettier {{CONFIG_FILE}}.prettierrc CI + pre-commit
PythonBlack + isortpyproject.tomlCI + pre-commithook
JSON / YAML Prettier .prettierrc CI + pre-commit hook

Key formatting rules:

  • Indentation: {{INDENT}}2 spaces
  • Max line length: {{LINE_LENGTH}}100 characters
  • Semicolons: {{SEMICOLONS}} required
  • Trailing commas: {{TRAILING_COMMAS}}all (ES2020)
  • SingleQuotes: quotes:single {{QUOTES}} quotes

Auto-format on save: RecommendedEnabled via configure.vscode/settings.json your(see IDE with the project's formatter settings.local-development-setup.md)

2.2 Linter

Language Tool Config Rules Severity
TypeScript {{LINTER}}ESLint + TypeScript-ESLint {{CONFIG_FILE}}Error = blocks CI, Warn = review
PythonRuff / Flake8pyproject.toml.eslintrc.json Error = blocks CI

Linting rules that are errors (block CI):

  • no-unused-variables
  • @typescript-eslint/no-explicit-any (TypeScript)— no any types
  • no-console (except console.error in productionserver codecode)
  • Import ordering violations@typescript-eslint/no-unused-vars
  • TypeSQL safetystring violationsconcatenation (custom Drop rule)

Disable linting inline (sparingly):

// eslint-disable-next-line @typescript-eslint/no-consoleexplicit-any -- debuggingExternal productionlibrary issue,returns removeuntyped beforeresponse
mergeconst console.log('Debug:',rawResponse: data)any = await externalLibrary.call();

Every inline disable must have a comment explaining why.

2.3 Editor Config

# .editorconfig (root of repo)
root = true

[*]
indent_style = {{INDENT_STYLE}}
indent_size = {{INDENT_SIZE}}
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

3. Git Conventions

3.1 Commit Message Format

Standard: Conventional Commits

Format:

<type>(<scope>): <description>

[optional body]

[optional footer(s)]

Types:

Type When to Use
feat New feature
fix Bug fix
docs Documentation only
style Formatting changes (no logic change)
refactor Code change that neither fixes a bug nor adds a feature
test Adding or updating tests
chore Build system, dependency updates, CI changes
perf Performance improvements
cisecurity CI/CDSecurity configurationfix changesor hardening

Examples:Drop examples:

feat(auth): add magicbcrypt linkrounds loginconfiguration flowvia env var
fix(cart)transactions): prevent double-chargespend onwith networkper-user retrylock
docs(api)test(db): updateadd userassertion endpointthat examplesusers table has no balance column
security(middleware): add CSRF protection to all mutating endpoints
chore: upgrade typescriptNext.js to 5.315.x patch

Breaking changes:

feat(api)!: rename user endpoint from /usersapi/transactions/send to /accountsapi/transactions/remittance

BREAKING CHANGE: All clients must update API calls from /api/users to /api/accountsnew path

Enforced by: commitlint + husky pre-commit hook

3.2 Branch Naming Convention

Type Pattern Example
Feature feature/MC-{{TICKET}}task-id}-short-description feature/AUTH-123-magic-linkMC-042-rate-limiting
Bug fix fix/MC-{{TICKET}}task-id}-short-description fix/CART-456-MC-051-double-chargespend
Hotfix hotfix/MC-{{TICKET}}task-id}-short-description hotfix/SEC-789-xss-fixMC-060-jwt-bypass
ReleaseSecurity release/v{security/MC-{VERSION}}task-id}-description release/v2.4.0security/MC-070-csrf-fix
Chore chore/MC-{{TICKET}}task-id}-short-description chore/DEP-012-MC-080-upgrade-reactdeps

Rules:

  • Lowercase only
  • only;
  • Hyphens,hyphens, not underscores
  • IncludeAlways ticketinclude number
  • Mission
  • KeepControl descriptiontask shortID (< 5 words)MC-{id})

3.3 PR Title & Description Format

Title format: Same as commit message format:message: type(scope): description

PR template: .github/pull_request_template.md — auto-loaded on PR creation

Required PR description sections:

  1. What: What does this PR do? (1-3 bullet points)
  2. Why: WhyWhich wasMission Control task / acceptance criterion?
  3. Pass-through model check: Does this change made?touch (context,DB ticketschema? link)If yes, confirm no balance/CVV columns added
  4. How to testTests: HowWhat shouldtests thewere reviewer verify it works?added/modified?
  5. ScreenshotsSecurity: RequiredDoes forthis UItouch changes
  6. auth,
  7. Checklist:payments, Pre-mergeor checklistinput (auto from template)validation?

3.4 PR Size Guidelines

Size Lines Changed StatusAction
Small < 200 Ideal Review— review same daysession
Medium 200-500400 AcceptableReview within 24h
Large 500-1000400-800 Needs justification Split— split if possible
Extra Large > 1000800 Exceptional only Must— must be pre-approved by John

Tips for keeping PRs small:

  • One PR per feature/fix
  • Separate refactoring from feature work
  • Use feature flags to deploy incomplete features

4. Code Review Guidelines

4.1 What to Look For (Validator Agent Checklist)

Reviewers shouldmust check:

  • LogicPass-through ismodel invariant: no balance column; no card_number or cvv
  •  Parameterized SQL — no string interpolation in any DB query
  •  bcrypt used for passwords — SHA-256 explicitly forbidden
  •  JWT secret not hardcoded; not in response body
  •  Rate limiting applies to new auth/transaction endpoints
  •  Input validation (Zod schema) on all new request bodies
  •  Error messages don't leak internal state
  •  Fee calculations correct and(0.5% handlesremittance; edge1% casesQR)
  • Tests cover the change (and edge cases)
  •  No obvious security issues (input validation, auth checks, SQL injection)
  •  Error handling is appropriate
  •  Performance: no N+1 queries, no unbounded operations
  •  Code follows naming and organization standards
  •  Documentation updated if behavior changedcases

Reviewers should NOT block on:

  • Personal style preferences thatcovered aren'tby inPrettier/ESLint (the standardslinter decides)
  • Minor refactors not related to the PR's scope
  • Hypothetical future requirements

4.2 Review Turnaround Expectations

PR Type First review Re-review after changes
CriticalSecurity / HotfixCritical fix WithinSame {{HOTFIX_SLA}} hoursWithin {{HOTFIX_RESLA}} hourssession
Standard feature Within {{STANDARD_SLA}}1 business hoursWithin {{STANDARD_RESLA}} hourssession

4.3 Approval Requirements

Director)ValidatorDirector)
Branch Required Approvals Notes
main / develop {{MAIN_APPROVALS}}Validator agent approval + John (includingAI CODEOWNER) sign-off
Feature branches {{FEATURE_APPROVALS}} agent approval
Hotfix {{HOTFIX_APPROVALS}}John (emergency:AI 1) Post-mergeor fullAlem reviewBašić (CEO) approval — async OK

4.4 Constructive Review Feedback

Use these prefixes to set expectations:

  • nit: — Minor issue, not a blocker: nit: prefer const hereblocker
  • question: — Need clarification, not a change request: question: why did you choose X over Y?clarification
  • suggestion: — Improvement idea,idea (not required: suggestion: this could be simplified with Xrequired)
  • blocker: — Must be fixed before merge:merge blocker: this allows(e.g., SQL injection
risk,

Tonebcrypt guidelines:

  • Comment on code, not the author
  • Explain the why: "This can cause N+1 queries, which will slow down under load" > "Wrong"
  • Acknowledge good code: "Nice approach here — much cleaner than what we had"
  • Be kind, be direct, be specificbypass)

5. Testing Standards

5.1 Test Naming Conventions

// Format:Vitest format: describe → it('should [expected behavior] when [condition]')
describe('hashPassword', () => {
  it('should return 404a whenbcrypt userhash doesstarting notwith exist'$2', async () => { ... });
  it('should hashreject theSHA-256 passwordhashes beforeat storing'verify time', async () => { ... });
  it('should throwreject ValidationErrorpasswords whenlonger emailthan is1000 invalid', async () => { ... });

// Describe blocks for grouping
describe('UserService', () => {
  describe('createUser', () => {
    it('should create user with valid input', async () => { ... });
    it('should throw when email already exists'characters', async () => { ... });
});

describe('calculateRemittanceFee', () => {
  it('should return 5 NOK fee for 1000 NOK amount', () => { ... });
  it('should reject amounts below 100 NOK', () => { ... });
});

5.2 Test File Organization

src/drop-app/__tests__/
├── users/auth.test.ts              # Unit: bcrypt, JWT, password validation
├── user.service.validation.test.ts        # Unit: input validation, XSS, injection
├── user.service.transactions.test.ts      # UnitUnit: testsfee calculation, transaction logic
├── user.service.rates.test.ts             # Unit: exchange rates
├── api-endpoints.test.ts     # Integration: all 26 API routes
├── db.test.ts                # Integration: schema compliance, FK constraints
├── middleware.test.ts        # Integration: rate limiting, auth, CSRF
├── api-benchmarks.test.ts    # Performance: bcrypt timing, DB queries
├── feature-flags.test.ts     # Unit: feature flag behavior
├── regression-suite.test.ts  # Regression: critical path smoke
├── sumsub-integration.test.ts # IntegrationIntegration: testsKYC webhook mock
├── cards-integration.test.ts  # Integration: cards (feature-flagged)
└── e2e/
    ├── user-flows.spec.ts    # E2E: registration, login
    ├── full-flows.spec.ts    # E2E: remittance, QR payment
    └── input-chaos.spec.ts   # E2E: 20+ validation edge cases

5.3 Mocking Guidelines

// PREFERRED: Mock BaaS at the module level jest.(NEXT_PUBLIC_SERVICE_MODE=mock)
// The mock mode is configured via env var — no vi.mock() needed for BaaS

// PREFERRED for unit tests: vi.mock for db queries
vi.mock('../external/email-service'@/lib/db', () => ({
  db: {
    query: vi.fn(),
    run: vi.fn(),
  }
}));

// PREFERRED:For Useintegration testtests: doublesuse thatreal matchSQLite thein-memory interface
const mockEmailService: EmailService = {
  send: jest.fn().mockResolvedValue({ id: 'msg-123' }),
};DB
// AVOID:(configured Over-mockingin internalsvitest.config.ts — no mock needed)

// AVOID:NEVER Testsmock thatthe testpass-through mocks,model notassertions behaviorin db.test.ts
// NEVER mock the bcrypt rejection tests in auth.test.ts

5.4 Coverage Requirements

Layer Lines Branches Notes
BusinessAuth logicmodule ≥ {{BIZ_COV}}%100% ≥ {{BIZ_BRANCH}}%100% StrictlyFintech security — strictly enforced
ControllersTransaction /logic100%100%Fee calculation — strictly enforced
API handlers {{CTRL_COV}}%80% {{CTRL_BRANCH}}%70%
UtilitiesInput validation {{UTIL_COV}}%90% ≥ 85% Security-critical
DB layer≥ 90%Compliance assertions required
Overall minimum {{MIN_COV}}%80% CI gate

6. Documentation Standards

6.1 When to Add Comments

// GOOD — explains WHY (not obvious from code)
// RetryBcrypt uprounds set to 3 times because the payment API has transient failures on high load
for12 (letnot i10) =per 0;NFR-SEC02 ifintech < 3; i++) { ... }standard.
// BADUsing 10 restatesrounds WHATwould (obviousbe fromfaster code)but //falls Loopbelow 3security timesthreshold.
forconst (let iBCRYPT_ROUNDS = 0;parseInt(process.env.BCRYPT_ROUNDS i?? <'12', 3; i++) { ... }10);

// GOOD — documents non-obvious behaviorbusiness rule
// Note:Fee Thisis functioncalculated mutateson thegross input array for performance. Callers mustamount, not total debit (gross + fee)
// passThis arraysis theyrequired intendby toDrop's usepass-through afterwards.model function(ADR-003)
sortInPlace(arr:const number[]):fee void= {amount ...* }REMITTANCE_FEE_RATE;

// BAD — restates what code says
// Multiply amount by fee rate
const fee = amount * REMITTANCE_FEE_RATE;

Comment when:

  • TheA reasonfintech forbusiness arule is implemented (always cite the rule)
  • A security decision iswas non-obviousmade (e.g., why bcrypt rounds = 12)
  • A workaround exists for an external librarysystem bug (include the bug link)
  • The code is intentionally doing something that looks wrong

Don't comment when:

  • The code explains itself
  • The test explains the expected behaviorquirk

6.2 JSDoc / DocstringADR Format

/**
 * Creates a new user account and sends a verification email.
 *
 * @param dto - User creation data (email must be unique)
 * @returns The created user object, or a typed error
 * @throws {RateLimitError} If the creation rate limit is exceeded
 * @example
 * const result = await createUser({ email: '[email protected]', name: 'Alice' });
 * if (!result.ok) handleError(result.error);
 */
async function createUser(dto: CreateUserDto): Promise<Result<User>> { ... }

6.3 README Requirements

Every repository/service MUST have a README with:

  1. What — One sentence describing the service
  2. Quick start — How to run it locally (3-5 commands)
  3. Key commands — dev, test, build, lint
  4. Links — Architecture doc, API docs, team channel

6.4 ADR Requirements

Write an Architecture Decision Record (ADR) when:

  • ChoosingChanging athe newdatabase major(SQLite dependency→ PostgreSQL)
  • Changing the techauthentication stackmechanism (DOB → BankID)
  • MakingModifying the fee model (new corridor, new fee rate)
  • Adding a significantnew architecturalBaaS change
  • Explicitly choosing NOT to do something commonpartner

ADR location: {{ADR_DIR}}/comms/decisions/YYYY-MM-DD-decision-title.md

ADR

6.3 template:API SeeDocumentation

When adding a new API endpoint, update {{ADR_TEMPLATE}}docs/backend/API-REFERENCE.md:

  • Method + path
  • Authentication required (Yes/No)
  • Request body schema (Zod type → JSON example)
  • Response schema (success + all error codes)

7. Security Coding Practices — Drop Fintech Standards

phone NoHIGH/CRITICAL
Practice Rule
Input validationValidate ALL external input at system boundaries. Use schema validation (Zod / Joi / Pydantic)
SQL queries NEVER use string interpolation in SQL.interpolation. Always use parameterized queries /(better-sqlite3 ORM? placeholders)
AuthenticationPassword hashing NEVERbcrypt implementONLY authwith from12 scratch.rounds Usein battle-testedproduction. librariesSHA-256 hashes are REJECTED at login
SecretsJWTjose library only. Secret via JWT_SECRET env var. Fail fast if missing
Input validationZod schemas on ALL API route request bodies. Server-side only — never trust client
Pass-through model NEVER hardcodeadd secrets.balance Usecolumn environmentto variablesusers. sourcedNEVER fromadd secretcard_number manageror cvv to cards
Rate limitingDB-backed rate limiter (not in-memory). Auth: 10/min; General: 60/min
CSRFCSRF token required on all POST/PATCH/DELETE endpoints
Cookie settingsJWT: httpOnly, SameSite=Strict, Secure in production
Logging NEVER log PII,passwords, passwords,JWT tokens, card numbers, or paymentfull data
DependenciesReview new dependencies for known CVEs before addingnumbers
Error messages NEVER expose internalDB detailserrors, (stack traces, DBor errors)internal state to users
HTML outputDependencies AlwaysRun escapenpm user-generatedaudit content.before Useadding framework'sany built-independency. escaping
File uploadsValidate type, size, and content. Never execute uploaded filesCVEs

Security review trigger: Any PR touching authentication, authorization,auth, payments, DB schema, or userinput datavalidation must be reviewed by {{SECURITY_REVIEWER}}Validator agent AND flagged to John (AI Director).


8. Performance Coding Practices

Practice Rule
Database queries AvoidOne bounded query per API call where possible. Use JOIN instead of N+1 — use include/JOIN or batch loading. Profile with query analyzer
Pagination Never load unboundedall lists.transactions Alwaysin history — always paginate or(default: stream20 per page)
Cachingbcrypt CachePassword expensivemax computationslength and= external1,000 APIchars calls.(validation Definebefore TTLbcrypt explicitlyto prevent DoS)
AsyncSQLite Use asyncWAL I/Omode for allconcurrent I/Oreads. operations.Serialize Neverwrites block(one theat eventa looptime)
IndexesNext.js AddUse databaseServer indexesComponents for anystatic/cached columndata; usedClient inComponents WHEREfor orinteractive ORDER BY at scaleUI
LargeBundle payloadssize CompressRun APInpm responses.run Streambuild largeand filescheck ratherbundle thananalyzer bufferingbefore inUI memorychanges


Approval

Role Name Date Signature
Author John (AI Director) 2026-02-23 Approved (AI)
ReviewerQA Lead Validator Agent 2026-02-23 Approved (AI)
ApproverTech Lead John 2026-02-23Approved
CEO (Alem)Alem BašićTBD