- ParcelSync version 0.6.0 with ePay CF extract ordering - ANCPI ePay in Current Integrations table - Static WORKSPACE_TO_COUNTY mapping documented - GisUat geometry select optimization documented - Feature count cache (5-min TTL) documented - ePay endpoint gotchas, auth flow, order flow - Cleaned outdated info, focused on actionable gotchas Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
36 KiB
ArchiTools — Project Context for AI Assistants
This file provides all context needed for Claude Code, Sonnet, or any AI model to work on this project from scratch.
Quick Start
npm install
npm run dev # http://localhost:3000
npx next build # verify zero errors before pushing
git push origin main # auto-deploys via Portainer webhook
Project Overview
ArchiTools is a modular internal web dashboard for an architecture/engineering office group of 3 companies:
- Beletage (architecture)
- Urban Switch (urbanism)
- Studii de Teren (geotechnics)
It runs on two on-premise servers, containerized with Docker, managed via Portainer CE.
Stack
| Layer | Technology |
|---|---|
| Framework | Next.js 16.x, App Router, TypeScript (strict) |
| Styling | Tailwind CSS v4, shadcn/ui |
| Database | PostgreSQL (10.10.10.166:5432) via Prisma v6 ORM |
| Storage | DatabaseStorageAdapter (PostgreSQL) — localStorage fallback available |
| File Storage | MinIO (10.10.10.166:9002 API / 9003 UI) — client configured, adapter pending |
| Auth | NextAuth v4 + Authentik OIDC (auth.beletage.ro) |
| Proxy | Traefik v3 on 10.10.10.199 (proxy server), SSL via Let's Encrypt |
| Deploy | Docker multi-stage, Portainer CE on 10.10.10.166 (satra) |
| Repo | Gitea at https://git.beletage.ro/gitadmin/ArchiTools |
| Language | Code in English, UI in Romanian |
Architecture Principles
- Module platform, not monolith — each module isolated with own types/services/hooks/components
- Feature flags gate module loading (disabled = zero bundle cost)
- Storage abstraction:
StorageServiceinterface with adapters (database default via Prisma, localStorage fallback) - Cross-module tagging system as shared service
- Auth via Authentik SSO — NextAuth v4 + OIDC, group→role/company mapping
- All entities include
visibility/createdByfields from day one - Company logos — theme-aware (light/dark variants), dual-rendered for SSR safety
Repository Structure
src/
├── app/ # Routing only (thin wrappers)
│ ├── (modules)/ # Module route pages
│ └── layout.tsx # App shell
├── core/ # Platform services
│ ├── module-registry/ # Module registration + types
│ ├── feature-flags/ # Flag evaluation + env override
│ ├── storage/ # StorageService + adapters
│ │ └── adapters/ # localStorage adapter (+ future DB/MinIO)
│ ├── tagging/ # Cross-module tag service
│ ├── i18n/ # Romanian translations
│ ├── theme/ # Light/dark theme
│ └── auth/ # Auth types + stub (future Authentik)
├── modules/ # Module business logic
│ ├── <module-name>/
│ │ ├── components/ # Module UI components
│ │ ├── hooks/ # Module-specific hooks
│ │ ├── services/ # Module business logic
│ │ ├── types.ts # Module types
│ │ ├── config.ts # Module metadata
│ │ └── index.ts # Public exports
│ └── ...
├── shared/ # Shared UI
│ ├── components/
│ │ ├── ui/ # shadcn/ui primitives
│ │ ├── layout/ # Sidebar, Header
│ │ └── common/ # Reusable app components
│ ├── hooks/ # Shared hooks
│ └── lib/ # Utils (cn, etc.)
├── config/ # Global config
│ ├── modules.ts # Module registry entries
│ ├── flags.ts # Default feature flags
│ ├── navigation.ts # Sidebar nav structure
│ └── companies.ts # Company definitions
docs/ # 16 internal technical docs
legacy/ # Original HTML tools for reference
Implemented Modules (16 total — 14 original + 2 new)
| # | Module | Route | Version | Key Features |
|---|---|---|---|---|
| 1 | Dashboard | / |
0.1.0 | KPI cards (6), activity feed (last 20), module grid, external tools |
| 2 | Email Signature | /email-signature |
0.1.0 | Multi-company branding, address toggle (BTG/US/SDT), live preview, zoom/copy/download |
| 3 | Word XML Generator | /word-xml |
0.1.0 | Category-based XML gen, simple/advanced mode, ZIP export |
| 4 | Registratura | /registratura |
0.5.0 | CRUD registry, dynamic doc types, bidirectional Address Book, threads, backdating, legal deadline tracking (6 categories, 18 types), recipient registration, document expiry, NAS network path attachments (A/O/P/T drives, copy-to-clipboard), detail sheet side panel, configurable column visibility, QuickLook attachment preview (images: zoom/pan, PDFs: native viewer, multi-file navigation), email notifications (Brevo SMTP daily digest, per-user preferences), compact registry numbers (single-letter company badge + direction arrow + plain number) |
| 5 | Tag Manager | /tag-manager |
0.2.0 | CRUD tags, category/scope/color, US/SDT seeds, mandatory categories, ManicTime bidirectional sync |
| 6 | IT Inventory | /it-inventory |
0.2.0 | Dynamic equipment types, rented status (purple pulse), 42U rack visualization, type/status/company filters |
| 7 | Address Book | /address-book |
0.2.0 | CRUD contacts (person OR institution), card grid, vCard export, Registratura reverse lookup, dynamic types (creatable), name OR company required (flexible validation), ContactPerson with department field, quick contact from Registratura (persons + institutions) |
| 8 | Password Vault | /password-vault |
0.4.0 | CRUD credentials, 9 categorii cu iconițe, WiFi QR code real, context-aware form, strength meter, company scope, AES-256-GCM encryption, utilizatori multipli per intrare (VaultUser[]: username/password/email/notes, colapsibil în form, badge în list) |
| 9 | Mini Utilities | /mini-utilities |
0.4.0 | Text case, char counter, percentage, TVA calculator (cotă configurabilă: 5/9/19/21% + custom), area converter, U→R, num→text, artifact cleaner, MDLPA, PDF compression (qpdf local lossless + iLovePDF API cloud lossy, streaming upload for large files), PDF unlock, DWG→DXF, OCR, color palette, paste on all drop zones, thermal drag-drop reorder, Calculator scară desen (real cm↔desen mm, 7 preseturi 1:20..1:5000 + custom) |
| 10 | Prompt Generator | /prompt-generator |
0.2.0 | Template-driven prompt builder, 18 templates (14 text + 4 image), search bar, target type filter |
| 11 | Digital Signatures | /digital-signatures |
0.1.0 | CRUD assets, drag-and-drop file upload, tag chips |
| 12 | Word Templates | /word-templates |
0.1.0 | Template library, 8 categories, version tracking, .docx placeholder auto-detection |
| 13 | AI Chat | /ai-chat |
0.2.0 | Multi-provider (OpenAI/Claude/Ollama/demo), project linking via Tag Manager, provider status badge |
| 14 | Hot Desk | /hot-desk |
0.1.1 | 4 desks, week-ahead calendar, room layout (window+door proportioned), reserve/cancel |
| 15 | ParcelSync | /parcel-sync |
0.6.0 | eTerra ANCPI integration, PostGIS database, background sync, 23-layer catalog, enrichment pipeline, owner search, per-UAT analytics dashboard, health check + maintenance detection, ANCPI ePay CF extract ordering (batch orders, MinIO PDF storage, dedup protection, credit tracking), static WORKSPACE_TO_COUNTY mapping, GisUat geometry select optimization, feature count cache (5-min TTL) |
| 16 | Visual Copilot | /visual-copilot |
0.1.0 | AI-powered image analysis — developed in separate repo (https://git.beletage.ro/gitadmin/vim), placeholder in ArchiTools, will be merged as module later |
Registratura — Legal Deadline Tracking (Termene Legale)
The Registratura module includes a full legal deadline tracking engine for Romanian construction permitting:
- 18 deadline types across 6 categories (Certificat, Avize, Completari, Urbanism, Autorizare, Litigii)
- Working days vs calendar days with Romanian public holiday support (including Orthodox Easter via Meeus algorithm)
- Chain deadlines (resolving one prompts adding the next — e.g., CU analiza → emitere, PUZ/PUD analiza → post-CTATU → emitere)
- Tacit approval (auto-detected when overdue + applicable type)
- Tabbed UI: "Registru" tab (existing registry) + "Termene legale" tab (deadline dashboard)
- Email notifications: daily digest via Brevo SMTP, per-user opt-in/opt-out preferences, N8N cron trigger
Key files:
services/working-days.ts— Romanian holidays,addWorkingDays(),isWorkingDay()services/deadline-catalog.ts— 18DeadlineTypeDefentries across 6 categoriesservices/deadline-service.ts—createTrackedDeadline(),resolveDeadline(),aggregateDeadlines()components/attachment-preview.tsx— QuickLook-style fullscreen preview (images: zoom/pan, PDFs: blob URL iframe, multi-file nav)components/deadline-dashboard.tsx— Stats + filters + tablecomponents/deadline-add-dialog.tsx— 3-step wizard (category → type → date preview)components/notification-preferences.tsx— Bell button + dialog with per-type togglescomponents/registry-table.tsx—CompactNumbercomponent: single-letter company badge (B/U/S/G), direction arrow (↓ intrat / ↑ iesit), plain number
Address Book — Flexible Contact Model
The Address Book supports both persons and institutions:
- Flexible validation: either
nameORcompanyrequired (not both mandatory) - Auto-type detection: when only company is set via quick-create, type defaults to "institution"
- ContactPerson sub-entities: each has
name,department,role,email,phone - Quick contact creation from Registratura: inline dialog with name + company + phone + email
- Display logic: if no name, company shows as primary; if both, shows "Name (Company)"
- Creatable types: dropdown with defaults (client/supplier/institution/collaborator/internal) + user-created custom types
Key files:
modules/address-book/types.ts—AddressContact,ContactPersoninterfacesmodules/address-book/components/address-book-module.tsx— Full UI (cards, detail dialog, form)modules/address-book/hooks/use-contacts.ts— Storage hook with search/filtermodules/address-book/services/vcard-export.ts— vCard 3.0 exportmodules/registratura/components/quick-contact-dialog.tsx— Quick create from registry
PDF Compression — Dual Mode (Local + Cloud)
Two compression routes, both with streaming upload support for large files (tested up to 287MB):
- Local (qpdf): lossless structural optimization — stream compression, object dedup, linearization. Safe, no font corruption. Typical reduction: 3-15%.
- Cloud (iLovePDF API): lossy image re-compression via iLovePDF REST API. Levels: extreme/recommended/low. Typical reduction: 50-91%. Requires
ILOVEPDF_PUBLIC_KEYenv var.
Architecture (zero-memory for any file size):
parseMultipartUpload()streams request body to disk (constant 64KB memory)- Scans raw file for multipart boundaries using
findInFile()with 64KB sliding window - Stream-copies PDF bytes to separate file
- Route handler processes (qpdf exec or iLovePDF API) and streams response back
Critical gotchas:
- Middleware body buffering:
api/compress-pdfroutes are excluded from middleware matcher (middleware buffers entire body at 10MB default) - Auth: route-level
requireAuth()instead of middleware (inauth-check.ts) - Unicode filenames:
Content-Dispositionheader usesencodeURIComponent()to avoid ByteString errors with Romanian chars (Ș, Ț, etc.) - Ghostscript
-sDEVICE=pdfwritedestroys font encodings — never use GS for compression, only qpdf
Key files:
app/api/compress-pdf/parse-upload.ts— Streaming multipart parser (zero memory)app/api/compress-pdf/extreme/route.ts— qpdf local compressionapp/api/compress-pdf/cloud/route.ts— iLovePDF API integrationapp/api/compress-pdf/auth-check.ts— Shared auth for routes excluded from middleware
Email Notifications (Brevo SMTP)
Platform-level notification service for daily email digests:
- Brevo SMTP relay via nodemailer (port 587, STARTTLS)
- N8N cron: weekdays 8:00 → POST
/api/notifications/digestwith Bearer token - Per-user preferences: stored in KeyValueStore (
notificationsnamespace), toggle global opt-out + 3 notification types - Digest content: urgent deadlines (<=5 days), overdue deadlines, expiring documents (CU/AC)
- HTML email: inline-styled table layout, color-coded rows (red/yellow/blue), per-company grouping
- Sender: "Alerte Termene" <noreply@beletage.ro>, test mode via
?test=truequery param
Key files:
src/core/notifications/types.ts—NotificationType,NotificationPreference,DigestSection,DigestItemsrc/core/notifications/email-service.ts— Nodemailer transport singleton (Brevo SMTP)src/core/notifications/notification-service.ts—runDigest(),buildCompanyDigest(),renderDigestHtml(), preference CRUDsrc/app/api/notifications/digest/route.ts— POST endpoint (N8N cron, Bearer auth)src/app/api/notifications/preferences/route.ts— GET/PUT (user session auth)
ParcelSync — eTerra ANCPI GIS Integration
The ParcelSync module connects to Romania's national eTerra/ANCPI cadastral system:
- eTerra API client (
eterra-client.ts): form-post auth, JSESSIONID cookie jar, session caching (9min TTL), auto-relogin, paginated fetching withmaxRecordCount=1000+ fallback page sizes (500, 200) - 23-layer catalog (
eterra-layers.ts): TERENURI_ACTIVE, CLADIRI_ACTIVE, LIMITE_UAT, etc. organized in 6 categories - PostGIS storage:
GisFeaturemodel with geometry column, SIRUTA-based partitioning,enrichmentJSONB field - Background sync: long-running jobs via server singleton, progress polling (2s), phase tracking (fetch → save → enrich)
- Enrichment pipeline (
enrich-service.ts): hits eTerra/api/immovable/listper parcel to extract NR_CAD, NR_CF, PROPRIETARI, SUPRAFATA, INTRAVILAN, CATEGORIE_FOLOSINTA, HAS_BUILDING, etc. - Owner search: DB-first (ILIKE on enrichment JSON) with eTerra API fallback
- Per-UAT dashboard: SQL aggregates (area stats, intravilan/extravilan, land use, top owners), CSS-only visualizations (donut ring, bar charts)
- Health check (
eterra-health.ts): pingseterra.ancpi.roevery 3min, detects maintenance by keywords in HTML response, blocks login when down, UI shows amber "Mentenanță" state - ANCPI ePay CF extract ordering: batch orders via
epay-client.ts, PDF storage to MinIO, dedup protection (queue + API level), credit tracking - Static county mapping:
WORKSPACE_TO_COUNTYincounty-refresh.ts— 42 verified entries, preferred over unreliable nomenclature API - Performance: GisUat queries use
selectto exclude geometry column; feature counts cached 5-min TTL - Test UAT: Feleacu (SIRUTA 57582, ~30k immovables, ~8k GIS features)
Key files:
services/eterra-client.ts— API client (~1000 lines), session cache, pagination, retryservices/eterra-layers.ts— 23-layer catalog with categoriesservices/sync-service.ts— Layer sync engine with progress trackingservices/enrich-service.ts— Enrichment pipeline (FeatureEnrichment type)services/eterra-health.ts— Health check singleton, maintenance detectionservices/session-store.ts— Server-side session managementservices/epay-client.ts— ePay HTTP client (login, cart, metadata, submit, poll, download)services/epay-queue.ts— Batch queue with dedup protectionservices/epay-storage.ts— MinIO storage helpers for CF extract PDFsservices/epay-counties.ts— County index mapping (eTerra county name → ePay alphabetical index 0-41)app/api/eterra/session/county-refresh.ts— StaticWORKSPACE_TO_COUNTYmapping, LIMITE_UAT geometry refreshcomponents/parcel-sync-module.tsx— Main UI (~4100 lines), 5 tabs (Export/Layers/Search/DB/Extrase CF)components/uat-dashboard.tsx— Per-UAT analytics dashboard (CSS-only charts)components/epay-tab.tsx— CF extract ordering tabcomponents/epay-connect.tsx— ePay connection widget
Infrastructure
Server: satra — 10.10.10.166 (Ubuntu, app server)
| Service | Port | Purpose |
|---|---|---|
| ArchiTools | 3000 | This app (tools.beletage.ro) |
| Gitea | 3002 | Git hosting (git.beletage.ro) |
| PostgreSQL | 5432 | App database (Prisma ORM) |
| Portainer CE | 9000 | Docker management + deploy |
| Uptime Kuma | 3001 | Service monitoring |
| MinIO | 9002 (API) / 9003 (UI) | Object storage |
| Authentik | 9100 | SSO (auth.beletage.ro) — active |
| N8N | 5678 | Workflow automation (daily digest) |
| Stirling PDF | 8087 | PDF tools |
| IT-Tools | 8085 | Developer utilities |
| FileBrowser | 8086 | File management |
| Netdata | 19999 | System monitoring |
| Dozzle | 9999 | Docker log viewer |
| CrowdSec | 8088 | Security |
Server: proxy — 10.10.10.199 (Traefik reverse proxy)
| Config | Path / Value |
|---|---|
| Static config | /opt/traefik/traefik.yml |
| Dynamic configs | /opt/traefik/dynamic/ (file provider, watch: true) |
| ArchiTools route | /opt/traefik/dynamic/tools.yml |
| SSL | Let's Encrypt ACME, HTTP challenge |
| Timeouts | readTimeout: 600s, writeTimeout: 600s, idleTimeout: 600s on websecure entrypoint |
| Response forwarding | flushInterval: 100ms (streaming support) |
IMPORTANT: Default Traefik v2.11+ has 60s readTimeout — breaks large file uploads. Must set explicitly in static config.
Deployment Pipeline
git push origin main
→ Gitea webhook fires
→ Portainer CE: Stacks → architools → "Pull and redeploy"
→ Toggle "Re-pull image and redeploy" ON → click "Update"
→ Portainer re-clones git repo + Docker multi-stage build (~2 min)
→ Container starts on :3000
→ Traefik routes tools.beletage.ro → http://10.10.10.166:3000
Portainer CE deploy: NOT automatic. Must manually click "Pull and redeploy" in Portainer UI after each push. The stack is configured from git repo http://10.10.10.166:3002/gitadmin/ArchiTools.
Docker
Dockerfile: 3-stage build (deps → builder → runner),node:22-alpine, non-root user- Runner stage installs:
gdal gdal-tools ghostscript qpdf(for PDF compression, GIS) Dockerfileincludesnpx prisma generatebefore build stepdocker-compose.yml: single service, port 3000, all env vars hardcoded (Portainer CE can't inject env vars)output: 'standalone'innext.config.tsis required@prisma/clientmust be independencies(not devDependencies) for runtime
Development Rules
TypeScript Strict Mode Gotchas
array.split()[0]returnsstring | undefined— use.slice(0, 10)insteadRecord<string, T>[key]returnsT | undefined— always guard with null check- Spread of possibly-undefined objects:
{ ...obj[key], field }— check existence first - lucide-react Icons: cast through
unknown→React.ComponentType<{ className?: string }> arr[0]isT | undefinedeven afterarr.length > 0check — assign to const first:const first = arr[0]; if (first) { ... }- Prisma
$queryRawreturnsunknown[]— always cast withas Array<{ field: type }>and guard access ?? ""on an object field typed{}produces{}notstring— use explicittypeof x === 'string'or'number'check
Conventions
- Code: English
- UI text: Romanian
- Components: functional,
'use client'directive where needed - State: localStorage via
useStorage('module-name')hook - IDs:
uuid v4 - Dates: ISO strings (
YYYY-MM-DDfor display, full ISO for timestamps) - No emojis in code or UI unless explicitly requested
Storage Performance Rules
- NEVER use
storage.list()followed bystorage.get()in a loop — this is an N+1 query bug list()fetches ALL items (keys+values) from DB but discards values, then eachget()re-fetches individually- ALWAYS use
storage.exportAll()(namespaced) orstorage.export(namespace)(service-level) to batch-load - Filter items client-side after a single fetch:
for (const [key, value] of Object.entries(all)) { ... } - After mutations (add/update), either do optimistic local state update or a single
refresh()— never both - NEVER store large binary data (base64 files) inside entity JSON — this makes list loading transfer tens of MB
- For modules with attachments: use
exportAll({ lightweight: true })for listing,storage.get()for single-entry full load - The API
?lightweight=trueparameter stripsdata/fileDatastrings >1KB from JSON values server-side - Future: move file data to MinIO; only store metadata (name, size, type, url) in the entity JSON
Module Development Pattern
Every module follows:
src/modules/<name>/
├── components/ # React components
├── hooks/ # Custom hooks (use-<name>.ts)
├── services/ # Business logic (pure functions)
├── types.ts # TypeScript interfaces
├── config.ts # ModuleConfig metadata
└── index.ts # Public exports
Middleware & Large Upload Routes
- Next.js middleware buffers the entire request body even if it only reads cookies/headers
- Default middleware body limit is 10MB — any upload route handling large files MUST be excluded
- Excluded routes pattern in
src/middleware.tsmatcher:api/auth|api/notifications/digest|api/compress-pdf - Excluded routes handle auth via
requireAuth()helper (src/app/api/compress-pdf/auth-check.ts) - To add a new large-upload route: (1) add to middleware matcher exclusion, (2) add
requireAuth()call in route handler next.config.tshasexperimental: { middlewareClientMaxBodySize: '500mb' }but this is unreliable withoutput: 'standalone'
eTerra / External API Rules
- ArcGIS REST API has
maxRecordCount=1000— always paginate withresultOffset/resultRecordCount - eTerra sessions expire after ~10min — session cache TTL is 9min, auto-relogin on 401/redirect
- eTerra goes into maintenance regularly — health check must detect and block login attempts
- Never hardcode timeouts too low — eTerra 1000-feature geometry pages can take 60-90s; default is 120s
- CookieJar + axios-cookiejar-support required for eTerra auth (JSESSIONID tracking)
- Page size fallbacks: if 1000 fails, retry with 500, then 200
- WORKSPACE_TO_COUNTY is the authoritative county mapping — static 42-entry map in
county-refresh.ts, preferred overfetchCounties()which 404s intermittently - GisUat.geometry is huge — always use Prisma
selectto exclude it in list queries; forgetting this turns 50ms into 5+ seconds - Feature counts are expensive — cached in global with 5-min TTL in UATs route; returns stale data while refreshing
ANCPI ePay Rules
- ePay county IDs = eTerra WORKSPACE_IDs (CLUJ=127, ALBA=10) — zero discovery calls needed
- ePay UAT IDs = SIRUTA codes — use
GisUat.workspacePk+sirutadirectly - EpayJsonInterceptor uses form-urlencoded (NOT JSON body) —
reqType=nomenclatorUAT&countyId=127 - saveProductMetadataForBasketItem uses multipart/form-data (form-data npm package)
- Document IDs are HTML-encoded in ShowOrderDetails —
"idDocument":47301767must be decoded before JSON parse - ePay auth is OpenAM — gets
AMAuthCookie, then navigate tohttp://(not https) for JSESSIONID - MinIO metadata must be ASCII — strip diacritics from values before storing
- Env vars:
ANCPI_USERNAME,ANCPI_PASSWORD,ANCPI_BASE_URL,ANCPI_LOGIN_URL,ANCPI_DEFAULT_SOLICITANT_ID,MINIO_BUCKET_ANCPI
Before Pushing
npx next build— must pass with zero errors- Test the feature manually on
localhost:3000 - Commit with descriptive message
git push origin main— Portainer auto-deploys
Company IDs
| ID | Name | Prefix |
|---|---|---|
beletage |
Beletage | B |
urban-switch |
Urban Switch | US |
studii-de-teren |
Studii de Teren | SDT |
group |
Grup | G |
Current Integrations
| Feature | Status | Notes |
|---|---|---|
| Authentik SSO | ✅ Active | NextAuth v4 + OIDC, group→role/company mapping |
| PostgreSQL | ✅ Active | Prisma ORM, KeyValueStore model, /api/storage route |
| MinIO | Client configured | 10.10.10.166:9002, bucket tools, adapter pending |
| AI Chat API | ✅ Multi-provider | /api/ai-chat — OpenAI/Claude/Ollama/demo; needs API key env |
| Vault Encryption | ✅ Active | AES-256-GCM server-side, /api/vault, ENCRYPTION_SECRET env |
| ManicTime Sync | ✅ Implemented | /api/manictime — bidirectional Tags.txt sync, needs SMB mount |
| NAS Paths | ✅ Active | \\newamun (10.10.10.10), drives A/O/P/T, hostname+IP fallback, src/config/nas-paths.ts |
| eTerra ANCPI | ✅ Active | ParcelSync module, eterra-client.ts, health check + maintenance detection |
| ANCPI ePay | ✅ Active | CF extract ordering, epay-client.ts, MinIO PDF storage, batch queue + dedup, /api/ancpi/* routes |
| PostGIS | ✅ Active | GisFeature model, geometry storage, spatial queries, used by ParcelSync |
| Email Notifications | ✅ Implemented | Brevo SMTP daily digest, /api/notifications/digest + /preferences, N8N cron trigger |
| N8N automations | ✅ Active (digest cron) | Daily digest cron 0 8 * * 1-5, Bearer token auth, future: backups, workflows |
| iLovePDF API | ✅ Active | Cloud PDF compression, ILOVEPDF_PUBLIC_KEY env, free tier 250 files/month |
| qpdf | ✅ Active | Local lossless PDF optimization, installed in Docker image (apk add qpdf) |
Model Recommendations
| Task Type | Claude | OpenAI | Notes | |
|---|---|---|---|---|
| Bug fixes, config | Haiku 4.5 | GPT-4o-mini | Gemini 2.5 Flash | Fast, cheap |
| Features, tests, UI | Sonnet 4.6 | GPT-5.2 | Gemini 3 Flash | Best value — Opus-class quality at Sonnet price |
| New modules, architecture | Opus 4.6 | GPT-5.3-Codex | Gemini 3 Pro | Complex multi-file, business logic |
Default: Sonnet 4.6 for most work. See ROADMAP.md for per-task recommendations.
Session Handoff Tips
- Read this
CLAUDE.mdfirst — it has all context - Read
ROADMAP.mdfor the complete task list with dependencies - Check
docs/for deep dives on specific systems - Check
src/modules/<name>/types.tsbefore modifying any module - Always run
npx next buildbefore committing - Push to
main→ Portainer auto-deploys via Gitea webhook - The 16 docs in
docs/total ~10,600 lines — search them for architecture questions
Documentation Index
| Doc | Path | Content |
|---|---|---|
| System Architecture | docs/architecture/SYSTEM-ARCHITECTURE.md |
Overall architecture, module platform design |
| Module System | docs/architecture/MODULE-SYSTEM.md |
Module registry, lifecycle, config format |
| Feature Flags | docs/architecture/FEATURE-FLAGS.md |
Flag system, env overrides |
| Storage Layer | docs/architecture/STORAGE-LAYER.md |
StorageService interface, adapters |
| Tagging System | docs/architecture/TAGGING-SYSTEM.md |
Cross-module tags |
| Security & Roles | docs/architecture/SECURITY-AND-ROLES.md |
Visibility, auth, roles |
| Module Dev Guide | docs/guides/MODULE-DEVELOPMENT.md |
How to create a new module |
| HTML Integration | docs/guides/HTML-TOOL-INTEGRATION.md |
Legacy tool migration |
| UI Design System | docs/guides/UI-DESIGN-SYSTEM.md |
Design tokens, component patterns |
| Docker Deployment | docs/guides/DOCKER-DEPLOYMENT.md |
Full Docker/Portainer/Nginx guide |
| Coding Standards | docs/guides/CODING-STANDARDS.md |
TS strict, naming, patterns |
| Testing Strategy | docs/guides/TESTING-STRATEGY.md |
Testing approach |
| Configuration | docs/guides/CONFIGURATION.md |
Env vars, flags, companies |
| Data Model | docs/DATA-MODEL.md |
All entity schemas |
| Repo Structure | docs/REPO-STRUCTURE.md |
Directory layout |
| Prompt Generator | docs/modules/PROMPT-GENERATOR.md |
Prompt module deep dive |