63 lines
4.7 KiB
Markdown
63 lines
4.7 KiB
Markdown
# CODE_STYLE — anwarpy.ai
|
|
|
|
> TypeScript, strict, boring on purpose. The existing codebase is the style guide; this file makes it explicit. Formatting tooling may be added later only if it matches, not fights, this file.
|
|
|
|
## 1. TypeScript
|
|
|
|
- `strict: true` (tsc is a commit gate — `cd web && npx tsc --noEmit` must exit 0).
|
|
- No `any`. Use `unknown` + narrowing, or the generated Payload types.
|
|
- Document types come from `src/payload-types.ts` (`npm run generate:types`, runs inside `npm run build`). **Never hand-write an interface that mirrors a collection doc** — import the generated one; hand-written mirrors drift and lie.
|
|
- Payload collection access/hook args: annotate the request/doc shape explicitly (existing pattern: `args: { doc?: {...}; req: { user?: {...} } | null }`).
|
|
- Avoid non-null `!`; if you're sure, prove it with a check or a comment stating the invariant.
|
|
|
|
## 2. Naming
|
|
|
|
| Thing | Convention | Example |
|
|
|---|---|---|
|
|
| Files | kebab-case | `secret-scan.ts`, `notify-form.tsx` |
|
|
| Route folders | Next convention, exact segment names in brackets | `posts/[slug]/route.ts` |
|
|
| Components | named exports (function name = file name, PascalCase) | `export function Topnav()` |
|
|
| Route handlers | Next default exports | `export async function POST(req: NextRequest)` |
|
|
| Lib functions | camelCase, exported individually | `lexicalToText`, `freeEarlyAccess` |
|
|
| Constants | SCREAMING_SNAKE | `READING_WORDS_PER_MINUTE`, `MEMBERSHIP_PRICE` |
|
|
| DB fields | camelCase in Payload | `readingMinutes`, `authorName` |
|
|
| Booleans | is/has/can/should or unambiguous verb | `billingEnabled`, `emailVerified` |
|
|
|
|
## 3. Error handling
|
|
|
|
- **Route handlers**: validate → early `NextResponse.json({ error: '<Arabic sentence>' }, { status })`. Statuses: 400 shape, 401 auth, 403 forbidden, 404 missing, 409 conflict (slug), 422 semantic rejection (secret scan), 429 rate-limited, 503 dependency honestly down.
|
|
- Error text is a **fixed Arabic sentence owned by the route** — specific about what failed («الدفع غير مفعّل خلال فترة الوصول المبكر.»). Never echo user input into the message; never expose stack traces or internal hosts; never a bare code with no words.
|
|
- Never swallow: no empty `catch`. If you catch to continue, comment why. Server-side failures get `console.error` with context (no secrets).
|
|
- **Honest failure is a feature** (PRD principle 1): if a dependency is down (mail transport, billing), return 503 with the true Arabic explanation instead of faking success. The notify route is the reference implementation.
|
|
|
|
## 4. Server vs client components
|
|
|
|
- Default: server components. `'use client'` only for forms and small interactions — check existing (`account-settings/client.tsx`, `notify-form.tsx`) before adding a new one.
|
|
- No secrets, no `process.env` (non-public), no Payload local API calls in client components. They talk to route handlers with `fetch` and render the returned Arabic message.
|
|
- No data-fetching libraries, no global client state (ARCHITECTURE §6).
|
|
|
|
## 5. Arabic & RTL in code
|
|
|
|
- UI copy is Arabic with «» quotes; keep strings verbatim from the PRD where they're contracts (honest 503s, notice strips).
|
|
- Latin islands inside RTL: wrap in `<bdi dir="ltr">` (`$10`, `anwarpy.ai`, code, `GPTBot`).
|
|
- Plurals/counts via `src/lib/format.ts` (`guidesLabel`, `minutesLabel`, `formatNumber`) — never inline Arabic number agreement.
|
|
|
|
## 6. Comments & docs
|
|
|
|
- Comments explain invariants and decisions, not syntax. Reference the contract: «L-02», «ADR-0003», «PRD §3.2» are valid shorthand — these docs are the map.
|
|
- When you change a documented decision, update the doc in the same PR (ADR for structural decisions — ARCHITECTURE §8).
|
|
- No commented-out code committed. Delete it; git remembers.
|
|
|
|
## 7. Git & PRs (Gitea)
|
|
|
|
- `main` is deployable truth. Feature branches: `launch/…`-style prefixes (`feat/…`, `fix/…`, `docs/…`).
|
|
- PRs via Gitea; merge = **POST** `/repos/ahmed/anwar-pi-website/pulls/{index}/merge` with `{"Do":"merge"}` (PUT → 405). Evidence-updating doc commits may go straight to `main` when they record verified facts.
|
|
- Commit messages: imperative, specific («fix: unwrap Lexical root in lexicalToText/countLexicalWords»), no «update stuff».
|
|
- Never commit: `.env*`, `media/`, `files-private/`, `backups/`, build output, `.tsbuildinfo`.
|
|
- Destructive ops (`reset --hard`, `push -f`, branch deletion) require the owner's explicit go-ahead.
|
|
|
|
## 8. Dependencies
|
|
|
|
- Reuse ladder (gstack): repo helper → stdlib → platform → installed dep → new dep. `stripe` is already installed (do not add a second payment lib); `sharp` exists for images; `tsx` for scripts.
|
|
- New runtime deps need a PR-level justification line in the description; dev deps for testing are pre-approved by TESTING.md.
|