docs: minimize CLAUDE.md — 51% smaller, fix stale data (#29)
* feat: import system improvements, INK2 fix, and Swedish text corrections - SIE parser: Windows-1252 and CP437 encoding detection and decoding - Bank file parser: add Nordea Business (Företag) CSV format - Bank file parser: improve format detection for SEB, Länsförsäkringar, generic CSV - INK2 engine: calculate årets resultat (7222) from income statement for open fiscal years - Dashboard: parallel Supabase queries, simplified dashboard page - Fix Swedish characters (å, ä, ö) in BAS data descriptions, validation messages, AI consent disclosures - Import wizard UI improvements across all steps - Migration: add 'bas_range' match type to sie_account_mappings constraint - Extensive new tests for SIE parser encoding and bank file parser Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: arcim migration wizard UX fixes, Sentry setup, and extension scaffolding Arcim migration wizard improvements: - Progress bar now excludes non-interactive steps (migrating/result) - Fix OAuth text to match target="_blank" behavior (new tab, not redirect) - Display month names instead of "Månad X" in preview - Fix Swedish typo "förifylla" in no-company-info message - Replace native checkboxes with shadcn Switch in options step - Add ConfirmationDialog before starting migration - Show progress percentage during migration - Add "Nästa steg" guidance and navigation links in result step - Add "Försök igen" button in error state (returns to options) - Add Bokio company ID help text (GUID from URL) - Add Fortnox integration add-on hint on connection failure Also includes: SIE import system improvements, INK2 fixes, Swedish text corrections, Sentry error tracking setup, and arcim-migration extension scaffolding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address PR review feedback - Fix OAuth error recovery blank page (restore provider from URL params) - Pass real userId to MigrationWizard instead of empty string - Remove ~50 debug console.log statements from sie-import.ts - Fix comment referencing account 3740 → 3741 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: comprehensive UI design audit and normalization Dashboard audit: - Fix muted-foreground contrast (4.31:1 → 5.08:1) for WCAG AA - Add prefers-reduced-motion media query for all animations - Replace border-l-2 accent anti-pattern with subtle full-border colors - Add aria-expanded to toggle buttons, role="status" to live counters - Fix touch targets on deadline buttons (28px → 36px) - Vary section spacing for rhythm (mb-12/mb-10/mb-8) - Remove unused imports and dead code Transactions audit + hardening: - Add pagination (200 per page) with "Ladda fler" button - Replace height animation with transform-only exit animation - Show batch progress in floating action bar during processing - Fix batch bar mobile overlap (bottom-20 on mobile) - Replace clickable badges with proper button elements - Add safe area padding to fullscreen swipe view - Add response.ok check to suggestion fetch - Add truncation to invoice number buttons Invoicing audit: - Remove border-l-4 accent pattern from invoice cards - Replace string concatenation with cn() utility Systemic sweep (34 files): - All page headings: font-bold → font-display font-medium (Fraunces) - All stat numbers: font-bold → font-display font-medium tabular-nums - All hard-coded blue/amber/emerald colors → design tokens - Remove all dark mode overrides (tokens handle automatically) - Tint pure white card background to 99% Design context added to CLAUDE.md with brand personality, aesthetic direction, and 5 design principles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: bookkeeping flow audit — design system, accessibility, UX - Replace raw <select> with shadcn Select component (JournalEntryForm) - Add confirmation dialog for account deletion (ChartOfAccountsManager) - Remove console.error from production code (JournalEntryList, JournalEntryForm) - Fix contradictory h-7/min-h-[44px] button sizing → h-10 (ChartOfAccountsManager) - Increase BAS catalog "Lägg till" touch target h-7 → h-9 - Improve loading state with spinner (JournalEntryList) - Improve empty state with icon, description, and guidance (JournalEntryList) - Add response.ok check on journal entry fetch - Add aria-expanded to entry expand buttons - Add tabular-nums to desktop debit/credit columns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: onboarding and empty state improvements Onboarding: - Replace font-serif with font-display (Fraunces) for brand consistency - Remove console.error calls from production code Empty states: - Fix broken /transactions/new link in EmptyTransactions (route doesn't exist) - Add actionHref fallback to EmptyCustomers when no onAction prop provided - Improve EmptyTransactions description copy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: clarify Swedish UX copy — terminology, errors, descriptions Terminology consistency: - "Försenad" → "Förfallen" for overdue invoices (customers/[id]) - "bokföringsorder" → actionable description in bookkeeping page - "verifikation har bifogats" → "underlag har bifogats" in doc warning - "Fortsätt ändå" → "Bokför utan underlag" (specific action) Error messages — replace generic "Fel" + "Något gick fel" with specific: - "Något gick fel vid bokföring" → "Transaktionen kunde inte bokföras" - "Något gick fel vid matchning" → "Transaktionen kunde inte matchas" - "Kunde inte hämta X" → "Kunde inte ladda X" + recovery hint - Add "Försök igen" guidance to all error toasts Page descriptions — replace redundant with actionable: - Invoices: "Skapa och hantera" → "Skicka, följ betalningar, skapa kreditnotor" - Bookkeeping: list of features → actionable description Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: design critique — dashboard affordance, reports description Dashboard: - Add ChevronRight indicator to clickable summary cards (Att få betalt, Koppla bank) to distinguish from static cards - Add cursor-pointer to linked cards Reports: - Replace feature list description with actionable guidance "Huvudbok, grundbok..." → "Generera skattedeklarationer..." Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace generic "Fel" error toasts with specific messages Deadlines: 5 generic "Fel" → specific per-action titles (create, toggle, edit, delete, load) Expenses detail: 5 generic "Fel" → specific per-action titles (load, approve, pay, credit, delete) Expenses new: 3 generic "Fel" → instructional validation messages (supplier name, supplier selection, invoice number) Customers: 1 generic "Fel" → specific load error with recovery hint All error toasts now follow pattern: title = what failed, description = how to recover Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: replace all remaining generic "Fel" error toasts (37 instances) Systematic sweep across 12 dashboard pages replacing generic title: 'Fel' with context-specific error titles: - Load errors: "Kunde inte ladda [resurs]" - Action errors: "[Åtgärd] misslyckades" - Validation: "[Fält] saknas" Every error toast now tells the user what failed without needing to read the description. Recovery hints added where missing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: import flow — normalize stat typography, remove console.warn - Replace font-bold with font-display font-medium on 13 stat numbers across SIEPreviewStep, BankFilePreviewStep, BankFileConfirmStep, ImportResultStep (missed by systemic sweep since these are in components/import/, not app/(dashboard)/) - Add tabular-nums to stat numbers displaying counts/currency - Remove console.warn in ArcimMigrationWorkspace Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: final cleanup — console statements, remaining font-bold stats Remove production console statements: - Step1EntityType: remove debug console.warn (dead code after onNext) - TransactionBookingDialog: remove console.error on doc link failure - JournalEntryAttachments: remove 3 console.error calls Normalize remaining font-bold stat displays: - SwipeCategorizationView: 3 instances (completion, amount displays) - NEDeclarationView: yearly result heading + value Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address Greptile review feedback loadMoreTransactions: add inbox item enrichment matching fetchTransactions - Paginated transactions now fetch invoice_inbox_items in parallel - Fixes missing document indicator, template suggestions, and inbox match card for transactions loaded via "Ladda fler" fetchAllPages: add maxPages guard (default 500) to prevent infinite loop - If Arcim gateway returns hasMore:true indefinitely, the loop now exits after 500 pages instead of running forever Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: minimize CLAUDE.md — remove derivable content, fix stale data Remove ~230 lines (51% reduction) of content that duplicates what's already in the source code (directory tree, function tables, type definitions, migration lists). Update migration count (63→65), add missing test helpers, fix cron job list. Keep all high-value sections: accounting guard rails, BAS accounts, VAT rutor, design context. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -25,70 +25,7 @@ npm run setup:extensions # Regenerate extension registry from extensions.config.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
app/
|
||||
(auth)/ Login, register, auth callback, MFA enroll/verify, password reset
|
||||
(onboarding)/ 6-step setup wizard
|
||||
(dashboard)/ Authenticated routes (invoices, customers, transactions,
|
||||
bookkeeping, reports, suppliers, supplier-invoices,
|
||||
receipts, deadlines, settings, help, import, extensions)
|
||||
(public)/ Public invoice action links (no auth), DPA, privacy policy
|
||||
api/ API routes organized by domain
|
||||
|
||||
components/
|
||||
ui/ shadcn/ui primitives
|
||||
bookkeeping/ Chart of accounts, journal entry form/list/review
|
||||
chat/ ChatWidget, ChatPanel
|
||||
extensions/ Extension marketplace UI, general/ workspace components
|
||||
transactions/ Transaction list, categorization, booking, VAT treatment
|
||||
(+ customers/, dashboard/, deadlines/, import/, invoices/,
|
||||
onboarding/, reports/, settings/, suppliers/)
|
||||
|
||||
extensions/general/ Config-driven extensions (see Extension System below)
|
||||
|
||||
lib/
|
||||
api/ Zod validation schemas (schemas.ts) and helpers (validate.ts)
|
||||
bookkeeping/ Core journal entry engine and all entry generators
|
||||
engine.ts Draft/commit workflow, balance validation, voucher numbering
|
||||
invoice-entries.ts Sales invoice journal entries (per-line VAT rates)
|
||||
transaction-entries.ts Bank transaction journal entries
|
||||
supplier-invoice-entries.ts Purchase invoice journal entries
|
||||
category-mapping.ts Category-to-BAS-account mapping
|
||||
mapping-engine.ts Rule-based auto-categorization (MCC, merchant patterns)
|
||||
bas-reference.ts BAS account catalog (~180 accounts)
|
||||
handlers/ Booking handler functions
|
||||
core/
|
||||
bookkeeping/ Period service, storno reversal, year-end closing
|
||||
documents/ Document archive (upload, versioning, SHA-256 integrity)
|
||||
audit/ Audit trail service
|
||||
tax/ Tax code service
|
||||
email/ EmailService interface + NoopEmailService default
|
||||
events/ Event bus (bus.ts, types.ts) — singleton, core emits, extensions subscribe
|
||||
extensions/ Extension system (loader, registry, types, hooks, context-factory)
|
||||
ai-consent.ts AI consent gate for extensions using third-party AI providers
|
||||
_generated/ Code-generated files (DO NOT EDIT)
|
||||
import/ SIE parser, bank file parser (10 Swedish bank formats)
|
||||
invoices/ VAT rules, invoice matching, PDF template, reminders
|
||||
reconciliation/ Bank reconciliation engine (4-pass matching)
|
||||
reports/ Financial reports (trial balance, income statement, balance sheet,
|
||||
VAT declaration, SIE export, general ledger, NE-bilaga, INK2, SRU export,
|
||||
full archive ZIP export)
|
||||
auth/ MFA helpers (mfa.ts) and API auth guard (require-auth.ts)
|
||||
supabase/ Client setup (client.ts = browser, server.ts = server)
|
||||
tax/ Tax calculations, deadlines, Swedish holidays
|
||||
vat/ VIES validation, moms box mapping
|
||||
init.ts Extension loader (idempotent, called by API routes)
|
||||
|
||||
types/index.ts Canonical type definitions (single source of truth)
|
||||
types/chat.ts Chat types
|
||||
tests/helpers.ts Mock factories and fixture builders
|
||||
supabase/migrations/ SQL migration files (63 files)
|
||||
extensions.config.json Extension opt-in configuration
|
||||
```
|
||||
|
||||
### Key Relationships
|
||||
## Key Architectural Relationships
|
||||
|
||||
- **All journal entry creation** routes through `lib/bookkeeping/engine.ts` via `createJournalEntry()`.
|
||||
- **API routes** that emit events must call `ensureInitialized()` (from `lib/init.ts`) at module level.
|
||||
@@ -96,47 +33,19 @@ extensions.config.json Extension opt-in configuration
|
||||
- **Supabase clients**: browser (`lib/supabase/client.ts`), server with cookies (`createClient()` from `server.ts`), service role (`createServiceClient()`).
|
||||
- **Extension system**: Opt-in via `extensions.config.json`. Core builds and runs with zero extensions.
|
||||
- **NE-bilaga, INK2 declaration, SRU export, and full archive export** are core reports (in `lib/reports/`), not extensions.
|
||||
- **AI consent gate** (`lib/extensions/ai-consent.ts`): AI extensions (`receipt-ocr`, `ai-categorization`, `ai-chat`) require user consent before API calls. The extension catch-all route checks consent and returns `403 AI_CONSENT_REQUIRED` if missing.
|
||||
- **AI consent gate** (`lib/extensions/ai-consent.ts`): AI extensions (`receipt-ocr`, `ai-categorization`, `ai-chat`) require user consent before API calls. Returns `403 AI_CONSENT_REQUIRED` if missing.
|
||||
- **Types**: All shared types in `types/index.ts` (single source of truth). Import via `import type { T } from '@/types'`. Event types live in `lib/events/types.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Authentication uses Supabase Auth with **email+password** (primary) and **magic link** (fallback). MFA via TOTP is supported.
|
||||
Supabase Auth with **email+password** (primary) and **magic link** (fallback). MFA via TOTP is supported.
|
||||
|
||||
### Auth Flow
|
||||
MFA is enforced **application-side** (middleware + API routes), **not** in RLS policies. Controlled by two env vars:
|
||||
|
||||
1. **Login** (`/login`) — Email+password with magic link toggle. After password login, checks MFA status.
|
||||
2. **Register** (`/register`) — Email+password signup. Supabase sends confirmation email. Strong password required (8+ chars, uppercase, lowercase, number, special character).
|
||||
3. **Password reset** (`/reset-password`) — Via recovery link from login page.
|
||||
4. **MFA verify** (`/mfa/verify`) — 6-digit TOTP code input after login when MFA is enrolled.
|
||||
5. **MFA enroll** (`/mfa/enroll`) — QR code + manual secret for authenticator app setup.
|
||||
|
||||
### MFA Enforcement
|
||||
|
||||
MFA is enforced **application-side** (middleware + API routes), **not** in RLS policies. This is controlled by two env vars:
|
||||
|
||||
| Env Var | Hosted (Vercel) | Self-hosted (Docker) |
|
||||
|---------|-----------------|----------------------|
|
||||
| `NEXT_PUBLIC_REQUIRE_MFA` | `true` | `false` (default) |
|
||||
| `NEXT_PUBLIC_SELF_HOSTED` | not set | `true` |
|
||||
|
||||
- **Self-hosted**: MFA is never enforced (`NEXT_PUBLIC_SELF_HOSTED=true` overrides). Users can still enable it voluntarily via Settings → Säkerhet.
|
||||
- **Hosted**: When `NEXT_PUBLIC_REQUIRE_MFA=true`, middleware redirects users without MFA to `/mfa/enroll` after onboarding, and users with MFA to `/mfa/verify` until AAL2 is achieved.
|
||||
|
||||
### Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `lib/auth/mfa.ts` | `isMfaRequired()` — checks env vars |
|
||||
| `lib/auth/require-auth.ts` | `requireAuth()` — API route guard (auth + MFA) |
|
||||
| `lib/supabase/middleware.ts` | Session refresh + MFA enforcement |
|
||||
| `app/(auth)/auth/callback/route.ts` | PKCE/magic link callback with MFA redirect |
|
||||
| `components/settings/SecuritySettings.tsx` | Password change + MFA enable/disable UI |
|
||||
|
||||
### Supabase MFA Setup
|
||||
|
||||
Enable TOTP in Supabase Dashboard → `Authentication` → `Multi-Factor Authentication`. No database migration needed — Supabase manages `auth.mfa_factors`, `auth.mfa_challenges`, and `auth.mfa_amr_claims` internally.
|
||||
- `NEXT_PUBLIC_SELF_HOSTED=true` → MFA never enforced (users can enable voluntarily)
|
||||
- `NEXT_PUBLIC_REQUIRE_MFA=true` (hosted/Vercel) → middleware redirects to `/mfa/enroll` or `/mfa/verify` until AAL2
|
||||
|
||||
---
|
||||
|
||||
@@ -144,23 +53,7 @@ Enable TOTP in Supabase Dashboard → `Authentication` → `Multi-Factor Authent
|
||||
|
||||
The engine (`lib/bookkeeping/engine.ts`) is the most critical system. All accounting flows route through it.
|
||||
|
||||
### Journal Entry Lifecycle
|
||||
|
||||
1. **`createDraftEntry(userId, input)`** — Creates `status: 'draft'`, `voucher_number: 0`. Validates balance. Emits `journal_entry.drafted`.
|
||||
2. **`commitEntry(userId, entryId)`** — Assigns voucher number via DB RPC (concurrent-safe). Sets `status: 'posted'`. Emits `journal_entry.committed`.
|
||||
3. **`createJournalEntry(userId, input)`** — Draft + commit in one call. This is what all entry generators use.
|
||||
4. **`reverseEntry(userId, entryId)`** — Storno reversal: swaps debit/credit, links via `reverses_id`/`reversed_by_id`.
|
||||
|
||||
### Entry Generators
|
||||
|
||||
| Function | File | Purpose |
|
||||
|----------|------|---------|
|
||||
| `createInvoiceJournalEntry()` | `invoice-entries.ts` | Debit 1510, Credit 30xx + 26xx VAT (per-line VAT rates) |
|
||||
| `createInvoicePaymentJournalEntry()` | `invoice-entries.ts` | Debit 1930, Credit 1510 |
|
||||
| `createCreditNoteJournalEntry()` | `invoice-entries.ts` | Reverses original invoice entry |
|
||||
| `createTransactionJournalEntry()` | `transaction-entries.ts` | Maps bank transactions via MappingResult |
|
||||
| `createSupplierInvoiceRegistrationEntry()` | `supplier-invoice-entries.ts` | Debit expense + 2641, Credit 2440 |
|
||||
| `createSupplierInvoicePaymentEntry()` | `supplier-invoice-entries.ts` | Debit 2440, Credit 1930 |
|
||||
**Lifecycle**: `createDraftEntry()` → `commitEntry()` (assigns voucher number via DB RPC). Convenience: `createJournalEntry()` does both in one call. Reversal via `reverseEntry()` (storno). Correction via `correctEntry()` in `lib/core/bookkeeping/storno-service.ts`.
|
||||
|
||||
### Key BAS Accounts
|
||||
|
||||
@@ -183,16 +76,7 @@ The `VatDeclarationRutor` type maps to the Swedish tax authority's momsdeklarati
|
||||
- **Ruta 48**: Ingående moms — input VAT (from 2641/2645)
|
||||
- **Ruta 49**: Moms att betala/återfå = (ruta 10 + 11 + 12) - ruta 48
|
||||
|
||||
The `VatDeclaration.breakdown.invoices` also includes `base25`/`base12`/`base6` for per-rate revenue breakdown in the UI.
|
||||
|
||||
### Bank Reconciliation
|
||||
|
||||
`lib/reconciliation/bank-reconciliation.ts` — 4-pass matching on account 1930:
|
||||
|
||||
1. `auto_exact` (0.95) — exact amount + exact date
|
||||
2. `auto_reference` (0.90) — exact amount + reference match
|
||||
3. `auto_date_range` (0.85) — exact amount + date ±3 days
|
||||
4. `auto_fuzzy` (0.75) — fuzzy amount (±0.01) + exact date
|
||||
`VatDeclaration.breakdown.invoices` also includes `base25`/`base12`/`base6` for per-rate revenue breakdown in the UI.
|
||||
|
||||
---
|
||||
|
||||
@@ -215,150 +99,19 @@ These rules exist for legal compliance, enforced by database triggers. **Never v
|
||||
|
||||
## Extension System
|
||||
|
||||
Extensions are opt-in plugins controlled by `extensions.config.json`. Core builds and runs with zero extensions.
|
||||
Extensions are opt-in plugins in `extensions/general/<name>/`, controlled by `extensions.config.json`. Core builds and runs with zero extensions. `npm run setup:extensions` generates static imports in `lib/extensions/_generated/` (runs automatically via `predev`/`prebuild`). Extensions **cannot** use dynamic imports (Next.js bundling).
|
||||
|
||||
### How It Works
|
||||
**API routes**: Dispatched via catch-all at `app/api/extensions/ext/[...path]/route.ts`. URL: `/api/extensions/ext/{extensionId}/{routePath}`. Path params extracted as `_paramName` search params.
|
||||
|
||||
1. Each extension has a `manifest.json` in `extensions/general/<name>/`.
|
||||
2. `extensions.config.json` lists enabled extension IDs.
|
||||
3. `npm run setup:extensions` generates files in `lib/extensions/_generated/` (static imports, workspace map, definitions).
|
||||
4. `predev`/`prebuild` hooks run this automatically.
|
||||
**Service provider patterns**:
|
||||
- *Interface registration* (email): Core defines noop default in `lib/email/service.ts`, extension calls `registerEmailService()`, core uses `getEmailService()`.
|
||||
- *Services record* (ai-categorization): Extension exposes via `services` property, core looks up via `extensionRegistry.get('id')?.services?.method(...)`.
|
||||
|
||||
### Available Extensions
|
||||
|
||||
| Extension | Category | Env Vars Required |
|
||||
|-----------|----------|-------------------|
|
||||
| `receipt-ocr` | import | `ANTHROPIC_API_KEY` |
|
||||
| `ai-categorization` | operations | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` |
|
||||
| `ai-chat` | operations | `ANTHROPIC_API_KEY`, `OPENAI_API_KEY` |
|
||||
| `invoice-inbox` | import | `ANTHROPIC_API_KEY` |
|
||||
| `calendar` | operations | — |
|
||||
| `enable-banking` | import | Enable Banking keys |
|
||||
| `email` | operations | `RESEND_API_KEY`, `RESEND_FROM_EMAIL` |
|
||||
|
||||
### Creating Extensions
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-extension.ts --name my-ext --sector general --category operations --description "..."
|
||||
```
|
||||
|
||||
Then add `"my-ext"` to `extensions.config.json` and run `npm run setup:extensions`.
|
||||
|
||||
**Constraints**: Extensions **cannot** use dynamic imports (Next.js bundling).
|
||||
|
||||
### Extension Interface
|
||||
|
||||
```typescript
|
||||
interface Extension {
|
||||
id: string; name: string; version: string; sector?: SectorSlug
|
||||
// All optional surfaces:
|
||||
apiRoutes?: ApiRouteDefinition[]
|
||||
eventHandlers?: ExtensionEventHandler[]
|
||||
services?: Record<string, (...args: any[]) => Promise<any>>
|
||||
sidebarItems?: SidebarItem[]
|
||||
mappingRuleTypes?: MappingRuleTypeDefinition[]
|
||||
settingsPanel?: SettingsPanelDefinition
|
||||
onInstall?(ctx: ExtensionContext): Promise<void>
|
||||
onUninstall?(ctx: ExtensionContext): Promise<void>
|
||||
}
|
||||
```
|
||||
|
||||
### Extension Context
|
||||
|
||||
Handlers receive an `ExtensionContext` with: `userId`, `extensionId`, `supabase` (pre-authenticated), `emit()`, `settings` (JSONB key-value), `storage` (Supabase Storage), `log` (scoped logger), `services`.
|
||||
|
||||
### Extension API Routes
|
||||
|
||||
Dispatched via catch-all at `app/api/extensions/ext/[...path]/route.ts`.
|
||||
URL scheme: `/api/extensions/ext/{extensionId}/{routePath}`
|
||||
|
||||
Path params extracted as `_paramName` search params (e.g., `/:id` → `searchParams.get('_id')`).
|
||||
|
||||
### Service Provider Patterns
|
||||
|
||||
**Interface registration** (email pattern): Core defines interface with noop default in `lib/email/service.ts`. Extension calls `registerEmailService()` at load time. Core uses `getEmailService()` — degrades gracefully.
|
||||
|
||||
**Services record** (ai-categorization pattern): Extension exposes functions via `services` property. Core looks up via registry: `extensionRegistry.get('ai-categorization')?.services?.findSimilarTemplates(...)`.
|
||||
|
||||
### Event Types (lib/events/types.ts)
|
||||
|
||||
`journal_entry.drafted/committed/corrected` | `document.uploaded` | `invoice.created/sent` | `credit_note.created` | `transaction.synced/categorized/reconciled` | `period.locked/year_closed` | `customer.created` | `receipt.extracted/matched/confirmed` | `supplier_invoice.received/extracted/confirmed`
|
||||
**Creating extensions**: `npx tsx scripts/create-extension.ts --name my-ext --sector general --category operations --description "..."`, then add to `extensions.config.json`.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
**Framework**: Vitest 4, `globals: true`, `environment: 'node'`. Tests colocated in `__tests__/` directories.
|
||||
|
||||
**Scope**: Business logic in `lib/` and API routes in `app/api/`. No component or E2E tests.
|
||||
|
||||
**Test helpers** (`tests/helpers.ts`):
|
||||
- `createMockSupabase()` / `createQueuedMockSupabase()` — Supabase mocks
|
||||
- `createMockRequest()`, `parseJsonResponse()`, `createMockRouteParams()` — API route testing
|
||||
- Fixture factories: `makeTransaction()`, `makeJournalEntry()`, `makeInvoice()`, `makeCustomer()`, `makeSupplier()`, `makeFiscalPeriod()`, `makeReceipt()`, `makeDocumentAttachment()`, `makeCompanySettings()`, etc.
|
||||
|
||||
**Patterns**:
|
||||
- Always mock `@/lib/supabase/server`
|
||||
- Use `vi.clearAllMocks()` and `eventBus.clear()` in `beforeEach`
|
||||
- API route tests: mock `@/lib/init` and lib functions, test auth (401), validation (400), not found (404), errors (500), happy path
|
||||
|
||||
---
|
||||
|
||||
## Database & Migrations
|
||||
|
||||
**Location**: `supabase/migrations/` — 63 files. Early migrations use sequential numbering (`20240101000001`–`20240101000038`), later ones use real timestamps (`20260223150836`+).
|
||||
**Next migration**: Use `mcp__plugin_supabase_supabase__apply_migration` which assigns timestamps automatically.
|
||||
|
||||
### Placeholder Migrations
|
||||
|
||||
Some migrations are no-op placeholders to preserve the numbering sequence:
|
||||
- **012** (`tax_codes_placeholder`) — Planned but never deployed. The system operates without the `tax_codes` table.
|
||||
- **023** (`document_version_chain_placeholder`) — Planned but never deployed. Document versioning columns/functions do not exist in production.
|
||||
|
||||
### Migration Rules
|
||||
|
||||
1. **Always enable RLS** and create `SELECT/INSERT/UPDATE` policies using `auth.uid() = user_id`
|
||||
2. **Always add `updated_at` trigger** using `update_updated_at_column()`
|
||||
3. **UUID primary keys**: `DEFAULT uuid_generate_v4()`
|
||||
4. **User ownership**: `user_id UUID REFERENCES auth.users ON DELETE CASCADE NOT NULL`
|
||||
5. **Never modify existing migrations** — create new ones
|
||||
6. **Never modify enforcement triggers** (migration 017) — legally required
|
||||
7. **Apply via Supabase MCP tool**: `mcp__plugin_supabase_supabase__apply_migration`
|
||||
|
||||
### Key Enforcement Triggers (migration 017)
|
||||
|
||||
- `enforce_journal_entry_immutability` — Blocks edits/deletes on posted/reversed entries
|
||||
- `enforce_journal_entry_line_immutability` — Blocks line modifications on committed entries
|
||||
- `enforce_period_lock` — Blocks writes to closed/locked fiscal periods
|
||||
- `block_document_deletion` — Prevents deletion of documents linked to committed entries
|
||||
- `enforce_retention_journal_entries` — 7-year retention enforcement
|
||||
- `set_committed_at` — Auto-sets timestamp on draft-to-posted transition
|
||||
- `calculate_retention_expiry` — Auto-sets `retention_expires_at = period_end + 7 years`
|
||||
|
||||
### Recent Migrations
|
||||
|
||||
- **`20260223150836_invoice_inbox`** — Invoice inbox table with document type classification, AI extraction, supplier/transaction matching, and receipt linking.
|
||||
- **`20260224101905_booking_template_embeddings`** — Booking templates with AI embeddings for suggestion matching.
|
||||
- **`20260224132419_user_description_matching`** — User description matching for transaction categorization.
|
||||
- **`20260224165254_prevent_overlapping_fiscal_periods`** — Exclusion constraint preventing overlapping fiscal periods per user.
|
||||
- **`20260224190818_enforce_fiscal_period_month_boundaries`** — Ensures fiscal periods start/end on month boundaries.
|
||||
- **`20260225103139_full_bas_2026`** — Full BAS 2026 account catalog, K2-excluded flag, and SRU code backfill.
|
||||
- **`20260226120553_expand_account_type_untaxed_reserves`** — Adds `untaxed_reserves` to `chart_of_accounts.account_type` CHECK constraint for BAS 21xx accounts (obeskattade reserver).
|
||||
- **`20260304191528_set_search_path_on_functions`** — Pins `search_path = public` on all 24 custom functions to prevent search_path injection.
|
||||
- **`20260306084837_invoice_delivery_note_sequences`** — Adds delivery note number sequence and `generate_invoice_number`/`generate_delivery_note_number` RPC functions.
|
||||
|
||||
---
|
||||
|
||||
## Type System
|
||||
|
||||
- All shared types live in `types/index.ts` — this is the single source of truth
|
||||
- Import via `import type { TypeName } from '@/types'`
|
||||
- When adding new domain types, add them to `types/index.ts`
|
||||
- Event types are the exception — they live in `lib/events/types.ts` (since they reference domain types)
|
||||
|
||||
---
|
||||
|
||||
## API Route Patterns
|
||||
## API Route Pattern
|
||||
|
||||
```typescript
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
@@ -388,6 +141,32 @@ export async function POST(request: Request) {
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
**Framework**: Vitest 4, `globals: true`, `environment: 'node'`. Tests colocated in `__tests__/` directories. Scope: business logic in `lib/` and API routes in `app/api/`. No component or E2E tests.
|
||||
|
||||
**Test helpers** (`tests/helpers.ts`): `createMockSupabase()`, `createQueuedMockSupabase()`, `createMockRequest()`, `parseJsonResponse()`, `createMockRouteParams()`, and fixture factories (`makeTransaction()`, `makeJournalEntry()`, `makeInvoice()`, `makeCustomer()`, `makeSupplier()`, `makeSupplierInvoice()`, `makeFiscalPeriod()`, `makeReceipt()`, `makeDocumentAttachment()`, `makeCompanySettings()`, `makeInvoiceInboxItem()`, `makeExtensionToggle()`, etc.).
|
||||
|
||||
**Patterns**: Always mock `@/lib/supabase/server`. Use `vi.clearAllMocks()` and `eventBus.clear()` in `beforeEach`. API route tests: mock `@/lib/init` and lib functions, test auth (401), validation (400), not found (404), errors (500), happy path.
|
||||
|
||||
---
|
||||
|
||||
## Database & Migrations
|
||||
|
||||
**Location**: `supabase/migrations/` — 65 files. Early migrations use sequential numbering (`20240101000001`–`20240101000038`), later ones use real timestamps.
|
||||
|
||||
### Migration Rules
|
||||
|
||||
1. **Always enable RLS** and create `SELECT/INSERT/UPDATE` policies using `auth.uid() = user_id`
|
||||
2. **Always add `updated_at` trigger** using `update_updated_at_column()`
|
||||
3. **UUID primary keys**: `DEFAULT uuid_generate_v4()`
|
||||
4. **User ownership**: `user_id UUID REFERENCES auth.users ON DELETE CASCADE NOT NULL`
|
||||
5. **Never modify existing migrations** — create new ones
|
||||
6. **Never modify enforcement triggers** (migration 017) — legally required
|
||||
7. **Apply via Supabase MCP tool**: `mcp__plugin_supabase_supabase__apply_migration`
|
||||
|
||||
---
|
||||
|
||||
## Skills, Git & CI
|
||||
|
||||
**Skills**: Always use `/frontend-design` for new UI. Use `langchain` for AI features. Use `vercel:deploy` for deployment.
|
||||
@@ -400,7 +179,7 @@ export async function POST(request: Request) {
|
||||
|
||||
## Deployment
|
||||
|
||||
Hosted on **Vercel**. Cron jobs in `vercel.json` (banking sync daily 05:00, deadlines 06:00, reminders 08:00, push notifications 09:00, tax deadlines yearly Jan 2, document verify weekly Sunday 03:00).
|
||||
Hosted on **Vercel**. Cron jobs defined in `vercel.json` (banking sync, deadlines, reminders, tax deadlines, document verification, sandbox cleanup).
|
||||
|
||||
**Core env vars**: `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, `NEXT_PUBLIC_APP_URL`, `CRON_SECRET`. **Auth env vars**: `NEXT_PUBLIC_REQUIRE_MFA` (set `true` on hosted), `NEXT_PUBLIC_SELF_HOSTED` (set `true` for Docker). Extension env vars only needed when that extension is enabled.
|
||||
|
||||
@@ -415,39 +194,28 @@ Never create a NUL/nul file: \gnubok\NUL
|
||||
|
||||
Swedish sole traders (enskild firma) and small business owners (aktiebolag) who need to manage their own bookkeeping. They are not accountants — they are professionals (consultants, freelancers, shop owners) who want to stay compliant without hiring one. They use gnubok in short, focused sessions: sending an invoice, categorizing bank transactions, filing a VAT declaration. Speed and clarity matter — every second spent in the app is a second away from their real work.
|
||||
|
||||
### Brand Personality
|
||||
### Brand & Aesthetic
|
||||
|
||||
**Minimal. Sharp. Efficient.**
|
||||
**Minimal. Sharp. Efficient.** The interface should feel like a well-made instrument: considered, quiet, and confident. Reference: Mercury (banking). Anti-reference: enterprise software (SAP/Oracle density).
|
||||
|
||||
gnubok is the tool that makes accounting feel handled. It doesn't try to be fun — it earns trust through precision, clarity, and speed. The interface should feel like a well-made instrument: considered, quiet, and confident. Think Mercury's calm authority applied to Swedish bookkeeping.
|
||||
|
||||
### Aesthetic Direction
|
||||
|
||||
- **Palette**: Grayscale foundation with restrained semantic colors — sage green (success/balance), terracotta (errors/overdue), ochre (warnings/attention). No loud brand color. The absence of color *is* the brand.
|
||||
- **Typography**: Fraunces (serif) for display headings — adds warmth and distinction to an otherwise minimal interface. Geist (sans) for body — clean, modern, excellent for tabular data. Tabular numbers everywhere financial data appears.
|
||||
- **Surfaces**: White/near-white cards on light gray backgrounds. Subtle borders (60% opacity). Soft shadows that suggest depth without drama. Dark mode follows the same restraint.
|
||||
- **Spacing**: Generous whitespace. Let content breathe. Dense data (tables, ledgers) should use tighter spacing but never feel cramped.
|
||||
- **Motion**: Subtle and purposeful. Stagger animations for list entry, spring easing for interactive feedback. Never decorative — always communicates state change.
|
||||
- **Icons**: Lucide — consistent 15px in navigation, slightly larger in empty states and actions.
|
||||
- **Reference**: Mercury (banking) — calm, trustworthy, clean data presentation, financial precision.
|
||||
- **Anti-reference**: Enterprise software (SAP/Oracle density, overwhelming dashboards with 50 widgets, toolbar soup).
|
||||
- **Palette**: Grayscale foundation with restrained semantic colors — sage green (success/balance), terracotta (errors/overdue), ochre (warnings/attention). No loud brand color.
|
||||
- **Typography**: Fraunces (serif) for display headings, Geist (sans) for body. Tabular numbers everywhere financial data appears.
|
||||
- **Surfaces**: White/near-white cards on light gray backgrounds. Subtle borders (60% opacity). Soft shadows. Dark mode follows the same restraint.
|
||||
- **Spacing**: Generous whitespace. Dense data (tables, ledgers) uses tighter spacing but never feels cramped.
|
||||
- **Motion**: Subtle and purposeful. Stagger animations for list entry, spring easing for feedback. Never decorative.
|
||||
- **Icons**: Lucide — 15px in navigation, slightly larger in empty states.
|
||||
|
||||
### Design Principles
|
||||
|
||||
1. **Clarity over cleverness.** Every element should be immediately understandable. Accounting is complex enough — the UI must never add confusion. Use familiar patterns, clear labels (in Swedish), and obvious hierarchy.
|
||||
|
||||
2. **Earned minimalism.** Remove everything that doesn't serve the user's task. But don't strip away context that prevents errors — in accounting, a missing detail can mean a compliance violation. Minimal means *considered*, not *sparse*.
|
||||
|
||||
3. **Numbers are first-class.** Financial data deserves typographic care: tabular-nums, proper alignment, adequate contrast, clear positive/negative distinction. A journal entry grid should feel as precise as a printed ledger.
|
||||
|
||||
4. **Trust through consistency.** Same patterns, same spacing, same behavior everywhere. Buttons work the same way. Cards look the same way. When the user learns one screen, they've learned them all.
|
||||
|
||||
5. **Speed is a feature.** Fast load, fast interaction, fast comprehension. Users are here to get a job done and leave. Optimize for the 90-second session: open app, categorize three transactions, close app.
|
||||
1. **Clarity over cleverness.** Every element immediately understandable. Clear labels (in Swedish), obvious hierarchy.
|
||||
2. **Earned minimalism.** Remove what doesn't serve the task, but don't strip context that prevents compliance errors.
|
||||
3. **Numbers are first-class.** Tabular-nums, proper alignment, adequate contrast, clear positive/negative distinction.
|
||||
4. **Trust through consistency.** Same patterns, spacing, and behavior everywhere.
|
||||
5. **Speed is a feature.** Optimize for the 90-second session.
|
||||
|
||||
### Accessibility
|
||||
|
||||
- Target: **WCAG AA** compliance
|
||||
- Minimum contrast ratio: 4.5:1 for text, 3:1 for UI components
|
||||
- All interactive elements keyboard-navigable with visible focus rings
|
||||
- Respect `prefers-reduced-motion` for users who disable animations
|
||||
- Color is never the sole indicator of state — always pair with icons, text, or shape
|
||||
- **WCAG AA**: 4.5:1 text contrast, 3:1 UI components
|
||||
- Keyboard-navigable with visible focus rings
|
||||
- Respect `prefers-reduced-motion`
|
||||
- Color never sole indicator of state — always pair with icons, text, or shape
|
||||
|
||||
Reference in New Issue
Block a user