ASAgriSense/Docs/Conventions

Development

Conventions

Package naming#

WorkspaceConventionExample
be-packagesfeature-<domain>feature-farmer-registry
fe-packagesfeature-<domain>-webfeature-farmer-registry-web
shared contracts<domain>-contractseudr-compliance-contracts
shared utilitiesdescriptive nounauth-permissions

Import rules#

Always import from the package barrel. Never use deep imports.

typescript
// ✅ correct — barrel import
import { EnrollmentWizard, useFarmers } from '@repo/feature-farmer-registry-web';

// ❌ wrong — deep import breaks the public API contract
import { EnrollmentWizard } from '@repo/feature-farmer-registry-web/src/components/EnrollmentWizard';

Cross-workspace imports are forbidden. apps/web cannot import from be-packages; fe-packages cannot import from be-packages. TypeScript path aliases enforce this — npm run typecheck will fail if violated.

TypeScript#

RuleDetail
Strict modeAll workspaces extend @repo/typescript-config. strictNullChecks, noImplicitAny, etc. are on.
No anyUse unknown for unknown shapes. Use type guards to narrow. No any except at system boundaries with comment justification.
Discriminated unionsPreferred for domain concepts with multiple shapes (e.g. risk level, farmer status).
Branded typesUse for IDs and domain scalars to prevent accidental mixing.
Deep conditional typesAvoid unless the simpler shape is genuinely insufficient.

Zod schemas as the source of truth#

Define Zod schemas in be-packages/feature-*/src/schema/ or shared/validation/. Infer TypeScript types from those schemas using z.infer. Never write a TypeScript type that duplicates a Zod schema shape.

typescript
// ✅ correct — type inferred from schema
const CreateFarmerSchema = z.object({
  firstName: z.string().min(1),
  nationalId: z.string().min(6),
  communityId: z.string().uuid(),
});
type CreateFarmerInput = z.infer<typeof CreateFarmerSchema>;

// ❌ wrong — manual type duplicates the schema
interface CreateFarmerInput {
  firstName: string;
  nationalId: string;
  communityId: string;
}

React#

RuleDetail
Function components onlyNo class components.
Hooks for stateKeep components lean. Extract stateful logic into named hooks.
No business logic in pagesRoute page files orchestrate layout and pass props. Business logic lives in feature packages.
Context sparinglyPrefer SWR for server state. Avoid creating new context providers for feature state.
No inline stylesUse Tailwind classes. Use @apply for repeated utility patterns in globals.css.

Testing#

RequirementDetail
Service unit testsAt least one Vitest unit test per new service function.
Route smoke testsAt least one smoke test per new API route (happy path + invalid input 422).
No mocked databasesIntegration tests hit a real Neon branch database. Don't mock the ORM layer.
Test file locationCo-located with source: feature-farmer-registry/src/services/farmer.service.test.ts
bash
# Run all tests
npm run test

# Run tests for one package
npm run test --workspace=feature-farmer-registry

# Run in watch mode
npm run test --workspace=feature-farmer-registry -- --watch

Linting & formatting#

ToolCommandNotes
ESLintnpm run lintv9 flat config. --max-warnings 0 enforced — zero warnings allowed.
Prettiernpm run formatAuto-formats all files. Run before committing.
TypeScriptnpm run typecheckFull type check across all workspaces.
Zero warnings policy
ESLint runs with --max-warnings 0 in CI. Any ESLint warning is treated as an error. Fix warnings before pushing.

API conventions#

ConventionDetail
ValidationEvery route uses @hono/zod-validator. 422 Unprocessable Entity on schema failure.
Error shape{ error: { code: string, message: string, details?: unknown } }
Tenant scopingEvery query includes WHERE org_id = $orgId extracted from JWT claims.
HTTP methodsGET (read), POST (create), PATCH (update), DELETE (delete). No PUT.
Route namingPlural nouns: /farmers, /farms, /surveys. Nested: /surveys/:id/questions

Code comments#

Default to writing no comments. Only add a comment when the why is non-obvious: a hidden constraint, a subtle invariant, or a workaround for a specific bug. Well-named identifiers communicate what the code does. If removing the comment would not confuse a future reader, don't write it.