14 — Technical Decisions (Locked)
Status: All decisions locked as of 2026-03-12. This document is the single source of truth for every dependency, library, and tooling choice in the Port Nimara CRM V1 rebuild.
1. Framework & Runtime
| Decision |
Choice |
Notes |
| Framework |
Next.js 15 (App Router) |
React 19, standalone output mode, next start behind nginx |
| Language |
TypeScript (strict mode) |
strict: true in tsconfig, no any escape hatches |
| Runtime |
Node.js 20 LTS |
Docker base image: node:20-alpine |
| Package manager |
pnpm |
Faster installs, strict dependency resolution |
2. UI Layer
| Decision |
Choice |
Notes |
| Styling |
Tailwind CSS 4 + Port Nimara design tokens |
Custom theme from brand guidelines. Full token system in 15-DESIGN-TOKENS.md |
| Typography |
Inter (UI), Georgia (formal docs), JetBrains Mono (data) |
Inter via Google Fonts. Brand fonts (Bill Corporate, Adobe Garamond) reserved for marketing materials — CRM uses approved in-house alternatives per brand guide p.14 |
| Component library |
shadcn/ui |
Copy-paste components (not npm package). Built on Radix UI primitives + Tailwind. ~50 components. Run npx shadcn@latest add <component> to scaffold into components/ui/. Fully customizable — we own the source. |
| Icons |
Lucide React |
Tree-shakeable, consistent stroke style, bundled with shadcn/ui |
| Data tables |
TanStack Table via shadcn DataTable |
Server-side pagination, sorting, filtering. Column definitions per entity. |
| Forms |
React Hook Form + Zod via shadcn Form |
Zod schemas shared between client validation and API endpoint validation |
| Toasts / notifications |
Sonner via shadcn Toast |
Stacked toasts, auto-dismiss, action buttons |
| Command palette |
shadcn Command (⌘K) |
cmdk under the hood. Global search, quick navigation, actions. |
| Rich text editor |
TipTap |
Headless, ProseMirror-based, extension architecture. React integration via @tiptap/react. Used in 3 contexts (see Section 8). Pre-built shadcn integration available ("Minimal Tiptap"). |
| Charts |
Recharts |
For dashboard widgets and report visualizations |
| Date handling |
date-fns |
Lightweight, tree-shakeable. No moment.js. |
2.1 TipTap Configuration (3 contexts)
- Email composer — Full toolbar: bold, italic, underline, lists, links, images. Merge field insertion via custom extension (or repurposed
@tiptap/extension-mention). HTML output for email body.
- Document template editor — Same as email composer plus: table support (
@tiptap/extension-table), page break hints, merge field tokens rendered as styled inline chips.
- Notes fields — Lightweight config: bold, italic, lists, links only. No toolbar — slash commands or floating menu. Markdown-like shortcuts enabled.
2.2 shadcn/ui Components Expected in Use
Core set (installed during Layer 0):
Button, Input, Label, Select, Textarea, Checkbox, RadioGroup, Switch, Dialog, Sheet, DropdownMenu, Command, Tabs, Table, Card, Badge, Avatar, Tooltip, Popover, Calendar, DatePicker, Form, Toast (Sonner), Skeleton, Separator, ScrollArea, AlertDialog, Accordion, Breadcrumb, NavigationMenu, Pagination, Progress, Slider
3. Data Layer
| Decision |
Choice |
Notes |
| Database |
PostgreSQL 16 |
Docker container, named volume, same Compose stack |
| ORM |
Drizzle ORM |
Type-safe, SQL-like syntax, push-based migrations. Schema in db/schema/ |
| Validation |
Zod |
Shared schemas: API validation + form validation + Drizzle type inference |
| Caching |
Redis 7 |
Session store, BullMQ backing store, rate limiting, Socket.io adapter |
4. Authentication & Authorization
| Decision |
Choice |
Notes |
| Auth library |
Better Auth |
Session-based auth with RBAC. No Keycloak dependency. |
| Session store |
Redis |
better-auth/plugins/redis for session persistence |
| Password hashing |
Argon2 (via Better Auth defaults) |
|
| RBAC |
4 roles: super_admin, admin, manager, agent |
Permissions defined per-role in lib/permissions.ts |
5. Real-time & Background Jobs
| Decision |
Choice |
Notes |
| WebSocket |
Socket.io |
Real-time notifications, live updates, presence. Redis adapter for scaling. |
| Job queue |
BullMQ |
Redis-backed. Recurring jobs (calendar sync, email sync, backups), event-driven jobs (EOI generation, webhook processing). Dashboard at /admin/queues. |
| Job dashboard |
bull-board |
Embedded in admin panel for queue monitoring |
6. File Storage & Documents
| Decision |
Choice |
Notes |
| Object storage |
MinIO (existing self-hosted instance, older version) |
S3-compatible. Credentials in env vars. SSE-S3 encryption at rest. DB-backed file metadata in files table. |
| E-signatures |
Documenso (self-hosted) |
Webhooks primary (instant). BullMQ fallback poll every 6 hours (rare safety net). |
| PDF generation |
@pdfme |
Template-based PDF generation. Branded layouts with port logo/colors. |
| Receipt OCR |
OpenAI Vision API |
Via standalone PWA at /scan. Offline queueing with IndexedDB. |
7. Email
| Decision |
Choice |
Notes |
| SMTP relay |
Poste.io (self-hosted) |
Per-user encrypted SMTP credentials stored in DB |
| Sending |
Nodemailer |
Direct SMTP send via user's configured account |
| IMAP sync |
imapflow |
Background job syncs threads, metadata in PostgreSQL, raw emails in MinIO |
| System email templates |
MJML |
Compiled to HTML at build time. Templates for: password set/reset, EOI notifications, follow-up reminders, invoices |
8. External APIs
| Decision |
Choice |
Notes |
| Google Calendar |
googleapis |
OAuth2 flow, 3 sync triggers (30min poll, on-login, on-navigation if stale > 5min) |
| Currency rates |
Frankfurter API |
Free, no API key, ECB rates. Cached daily. |
| AI (receipt OCR) |
OpenAI Vision API |
Only AI dependency. Scoped to receipt scanning. |
9. Testing
| Decision |
Choice |
Notes |
| Unit / integration |
Vitest |
Modern Jest replacement. Same describe/it/expect API. Natively supports TypeScript/ESM. Built on Vite — significantly faster. Next.js officially recommends it. |
| E2E |
Playwright |
Real browser automation. V1 scope: 5–6 critical workflow tests only. |
| Component tests |
Skipped for V1 |
Vitest covers logic; Playwright covers critical flows. Component-level tests deferred. |
9.1 Vitest Coverage Targets (V1)
- All business rule functions in
lib/business-rules/
- All Zod validation schemas
- API endpoint handlers (happy path + key error cases)
- Utility functions (date formatting, currency conversion, permission checks)
- Service layer functions (EOI generation, invoice calculation, email sending)
9.2 Playwright E2E Tests (V1)
5–6 critical workflows:
- Login → dashboard loads → navigate to clients
- Create client → link interest → generate EOI → verify PDF
- Create expense → upload receipt → verify in expense list
- Email compose → send → verify in thread
- Admin: create user → assign role → verify permissions
- Berth management: create berth → assign client → verify status transitions
10. DevOps & Infrastructure
| Decision |
Choice |
Notes |
| Containerization |
Docker Compose |
Services: crm-app, postgres, redis, minio, documenso, nginx |
| Reverse proxy |
nginx |
TLS termination, rate limiting, static asset caching |
| CI/CD |
GitHub Actions |
Lint → type-check → Vitest → build → deploy |
| Backups |
pg_dump → MinIO |
Nightly at 02:00, encrypted, 30-day retention |
11. Server-side State & API
| Decision |
Choice |
Notes |
| API style |
REST |
Consistent pattern for CRM frontend, website berth map, future integrations |
| Server state |
TanStack Query |
Client-side caching, automatic revalidation, optimistic updates |
| Client state |
Zustand |
Minimal UI state (sidebar collapse, active port, theme) |
| API docs |
OpenAPI / Swagger |
Auto-generated from Zod schemas. Available at /api/docs in dev. |
Decision Log
| Date |
Decision |
Rationale |
| 2026-02-xx |
Next.js 15 over Nuxt 3 / SvelteKit |
Largest AI training corpus, best Claude Code fluency |
| 2026-02-xx |
REST over tRPC |
One API pattern for all consumers, no dual-paradigm |
| 2026-02-xx |
PostgreSQL + Drizzle over NocoDB |
Relational integrity, type-safe ORM, proper migrations |
| 2026-02-xx |
Better Auth over Keycloak |
Simpler, no external service, built-in RBAC |
| 2026-02-xx |
Keep MinIO |
Working infrastructure, S3-compatible, no migration needed |
| 2026-03-12 |
shadcn/ui confirmed |
Copy-paste ownership, Radix primitives, Claude Code fluent |
| 2026-03-12 |
TipTap for rich text |
Headless ProseMirror, extension architecture, shadcn integration exists |
| 2026-03-12 |
Vitest + Playwright |
Vitest for unit/integration (fast, TS-native), Playwright for 5-6 critical E2E |
| 2026-03-12 |
Skip component tests V1 |
Logic covered by Vitest, flows covered by Playwright |
| 2026-03-12 |
MinIO stays (older version) |
Self-hosted instance works, no migration overhead |
| 2026-03-12 |
Port Nimara design tokens from brand guide |
Full token system in 15-DESIGN-TOKENS.md, replaces generic "Maritime tokens" |
| 2026-03-12 |
Inter as CRM UI font |
Brand guide approves Arial for in-house; Inter is the modern web equivalent, shadcn default |
| 2026-03-12 |
Georgia for formal generated docs |
Brand guide secondary font (Adobe Garamond) is commercial; Georgia is the approved in-house serif |