# 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 ` 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) 1. **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. 2. **Document template editor** — Same as email composer plus: table support (`@tiptap/extension-table`), page break hints, merge field tokens rendered as styled inline chips. 3. **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: 1. Login → dashboard loads → navigate to clients 2. Create client → link interest → generate EOI → verify PDF 3. Create expense → upload receipt → verify in expense list 4. Email compose → send → verify in thread 5. Admin: create user → assign role → verify permissions 6. 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 |