# Project: Receipt Scanner Web Application UI/UX Upgrade

## Architecture
- **Framework & Runtime**: Next.js 15 App Router, React 19, TypeScript 5.7 (strict), Tailwind CSS 3.4
- **Design System**: Zenith Silver (`#F6F9FF` background, `#FFFFFF` surface cards, `#E2E8F0` borders, `#000000` accents, crisp 0px sharp corners, Hanken Grotesk / Inter / JetBrains Mono typography)
- **Data & Storage**: Local-first IndexedDB (`idb`) with PostgreSQL / Drizzle ORM schema compatibility
- **Export Formats**: Dual-sheet `.xlsx` (ExcelJS), DATEV-compliant `.csv` (UTF-8 BOM), structured `.json`

## Feature Inventory
| # | Feature | Description | Milestone | Source | Status |
|---|---------|-------------|-----------|--------|:------:|
| 1 | Full-page Drag & Drop Overlay | High-visibility global drag-and-drop backdrop overlay across dashboard | M1 | ORIGINAL_REQUEST R1 | DONE |
| 2 | Dedicated Dropzone Component | Visual upload card supporting PDF, PNG, JPEG, WebP with laser scanline | M1 | ORIGINAL_REQUEST R1 | DONE |
| 3 | Batch Upload Drawer & Queue | Multi-file batch queue with instant thumbnail previews, live progress bars | M1 | ORIGINAL_REQUEST R1 | DONE |
| 4 | Error Boundaries & File Retry | Isolated per-file failure handling, corrupt file rejection without halting queue, quick retry/remove | M1 | ORIGINAL_REQUEST R1 | DONE |
| 5 | Side-by-Side Dual Pane Modal | 50/50 split review modal with document on LEFT and editable fields on RIGHT | M2 | ORIGINAL_REQUEST R2 | DONE |
| 6 | Interactive Document Viewer | CSS transform Zoom (0.25x–5x), Pan (grab/trackpad), Rotate, Fit-to-Page | M2 | ORIGINAL_REQUEST R2 | DONE |
| 7 | Bounding-Box Visual Cues | 2-way highlight synchronization between image bounding boxes and extracted form fields | M2 | ORIGINAL_REQUEST R2 | DONE |
| 8 | Dynamic Line Items Editor | Inline-editable table for line items (description, quantity, price, total) with add/delete row controls | M2 | ORIGINAL_REQUEST R2 | DONE |
| 9 | Field Audit & Immediate Save | "AI Extracted" vs "Manually Edited" indicators, 1-click revert, debounced IndexedDB auto-save, Prev/Next navigation | M2 | ORIGINAL_REQUEST R2 | DONE |
| 10 | Payment Method & Schema Extension | Added payment method selection and extended receipt schema | M2 | ORIGINAL_REQUEST R2 | DONE |
| 11 | Editable Table Affordances | Clear dotted underlines, hover edit cues, human-edited flags in LiveTable | M3 | ORIGINAL_REQUEST R3 | DONE |
| 12 | 3-Tier Status Badges | Standardized badges: Scanned (Emerald), Pending Review (Amber), Confirmed (Slate/Blue) | M3 | ORIGINAL_REQUEST R3 | DONE |
| 13 | Floating Batch Actions Toolbar | Multi-select toolbar for Bulk Export (Selected Only to XLSX/CSV/JSON), Bulk Categorize, Bulk Status Update, Bulk Delete | M3 | ORIGINAL_REQUEST R3 | DONE |
| 14 | Quick Search & Filter Chips Bar | Clickable chip filters for temporal ranges (Today/Week/Month/Year), status counters, categories, amount brackets | M3 | ORIGINAL_REQUEST R3 | DONE |
| 15 | Responsive Dashboard Navigation | Mobile-friendly sidebar drawer / bottom nav (`hidden md:flex`) eliminating horizontal clipping on <768px | M4 | ORIGINAL_REQUEST R4 | DONE |
| 16 | Interactive KPI Statistics Cards | Total Scanned, Monthly Spend, Pending Reviews, Average Accuracy cards with click-to-filter micro-interactions | M4 | ORIGINAL_REQUEST R4 | DONE |
| 17 | Accessible Information Hierarchy | WCAG AA contrast, crisp typography, clean header layout, zero horizontal overflow | M4 | ORIGINAL_REQUEST R4 | DONE |
| 18 | E2E & Unit Test Coverage | Comprehensive tests verifying R1–R4 features, build integrity, and typechecks | M5 | ORIGINAL_REQUEST AC | DONE |

## Milestones
| # | Name | Scope | Dependencies | Status |
|---|------|-------|-------------|--------|
| M1 | Ingestion & Batch Upload (R1) | Global dropzone, batch queue drawer, thumbnails, progress, retry/remove | none | DONE |
| M2 | Side-by-Side Inspector & Split Review (R2) | Dual-pane modal, DocumentViewer (zoom/pan), BBox sync, LineItemsEditor, audit badges | none | DONE |
| M3 | Interactive Table & Batch Operations (R3) | Cell edit affordances, status badges, BatchActionBar, FilterChipsBar, selection hooks | none | DONE |
| M4 | Accessible Hierarchy & Responsive Shell (R4) | Responsive Sidebar/TopNav, interactive KPI cards, mobile drawer, zero-overflow | M1, M2, M3 | DONE |
| M5 | E2E Test Suite & Final Verification | Test infra, Tier 1-5 tests, adversarial tests, `npm run build`, `npx tsc --noEmit` | M1, M2, M3, M4 | DONE |

## Interface Contracts

### M1 Ingestion ↔ Dashboard
- `BatchUploadDrawer`: Accepts `onComplete: (receipts: ProcessedReceipt[]) => void`, `isOpen: boolean`, `onClose: () => void`.
- `BatchUploadDropzone`: Accepts `onFilesSelected: (files: File[]) => void`.
- `GlobalDropzoneOverlay`: Listens to `window` drag events; triggers batch queue on file drop.

### M2 Inspector ↔ Table & Storage
- `ReceiptInspectorModal`: Accepts `receipt: ProcessedReceipt`, `isOpen: boolean`, `onClose: () => void`, `onSave: (updated: ProcessedReceipt) => void`, `onNavigate?: (direction: 'prev' | 'next') => void`.
- `ProcessedReceipt` schema additions:
  - `paymentMethod?: string`
  - `boundingBoxes?: Record<string, { x: number; y: number; width: number; height: number }>`
  - `editedFields?: Record<string, boolean>`
  - `originalExtraction?: Partial<ProcessedReceipt>`

### M3 LiveTable ↔ BatchActionBar & FilterChipsBar
- `useReceiptFilters`: returns `{ filteredReceipts, activePeriod, setPeriod, activeStatus, setStatus, activeCategory, setCategory, searchQuery, setSearchQuery, amountRange, setAmountRange, resetFilters }`.
- `useTableSelection`: returns `{ selectedIds, isSelected, toggleSelect, toggleSelectAll, clearSelection, selectAll, count }`.
- `BatchActionBar`: Accepts `selectedIds: string[]`, `receipts: ProcessedReceipt[]`, `onBulkDelete`, `onBulkCategorize`, `onBulkStatusUpdate`, `onBulkExport`, `onClearSelection`.

### M4 Responsive Shell ↔ Dashboard Layout
- `Sidebar`: Desktop fixed sidebar (`hidden md:flex`) and mobile drawer (`block md:hidden`) triggered via `TopNav` hamburger button with smooth backdrop transition and zero horizontal overflow.
- `TopNav`: Header with breadcrumb/view indicator, quick search trigger, user/workspace menu, and mobile drawer toggle button.
- KPI Statistics Cards in `page.tsx`: Interactive cards for `Total Scanned`, `Monthly Spend`, `Pending Reviews`, and `Average Accuracy` (calculated dynamically from AI confidence/status with micro-interaction hover states and click-to-filter triggers).

## Code Layout
```
src/
├── app/
│   ├── dashboard/
│   │   ├── layout.tsx                # Responsive shell (TopNav + mobile drawer + desktop Sidebar)
│   │   ├── page.tsx                  # Overview with KPI cards + Ingestion dropzone + LiveTable
│   │   ├── activity/page.tsx         # Activity archive with FilterChipsBar + LiveTable + BatchActionBar
│   │   ├── export/page.tsx           # Export hub with summary statistics & date filters
│   │   └── settings/page.tsx         # Settings & preferences
├── components/
│   ├── dashboard/
│   │   ├── BatchUploadDropzone.tsx   # [M1] Dedicated ingestion dropzone card (DONE)
│   │   ├── BatchUploadDrawer.tsx     # [M1] Multi-file upload queue & progress modal/drawer (DONE)
│   │   ├── GlobalDropzoneOverlay.tsx # [M1] Full-screen dragover overlay (DONE)
│   │   ├── ReceiptInspectorModal.tsx # [M2] 50/50 Dual-pane review modal (DONE)
│   │   ├── DocumentViewer.tsx        # [M2] Zoom, pan, rotate & bounding-box canvas (DONE)
│   │   ├── LineItemsEditor.tsx       # [M2] Line item table & editor (DONE)
│   │   ├── StatusBadge.tsx           # [M3] 3-tier status badges (DONE)
│   │   ├── BatchActionBar.tsx        # [M3] Multi-select batch operations floating bar (DONE)
│   │   ├── FilterChipsBar.tsx        # [M3] Responsive filter chips bar (DONE)
│   │   ├── LiveTable.tsx             # [M3] Spreadsheet table with cell edit indicators (DONE)
│   │   ├── Sidebar.tsx               # [M4] Responsive sidebar & mobile drawer (DONE)
│   │   ├── TopNav.tsx                # [M4] Header with search, mobile trigger, status (DONE)
│   │   └── KPICards.tsx              # [M4] Interactive KPI cards with micro-interactions (DONE)
└── lib/
    ├── hooks/
    │   ├── useReceiptFilters.ts      # [M3] Filtering hook (DONE)
    │   └── useTableSelection.ts      # [M3] Selection hook (DONE)
    ├── schema/
    │   └── receipt.ts                # [M2] Extended receipt schema (DONE)
    └── utils/
        └── boundingBoxes.ts          # [M2] Bounding box calculation & heuristics (DONE)
```

## Appendix: Database Least Privilege

**Why.** The app must connect to PostgreSQL with only the rights it actually
needs — not superuser. The docker-compose defaults make `receipt_user` the
database superuser (`POSTGRES_USER`). If the application is compromised, an
attacker holding the app's credentials would otherwise get full control of the
database: read every user's receipts, drop or alter tables, or grant themselves
rights. A restricted runtime role limits the blast radius to reading and
modifying rows.

**Two roles.**
- `receipt_user` — **owner / migration role**. Keeps full rights (DDL) and is
  used for migrations and schema init (`src/lib/db/init.ts`). Never the runtime
  connection in production.
- `receipt_app` — **runtime role** (created by `scripts/db-permissions.sql`).
  LOGIN, no superuser, no CREATEDB/CREATEROLE. Granted exactly: CONNECT on the
  database, USAGE on schema `public` (no CREATE), SELECT/INSERT/UPDATE/DELETE on
  all tables, USAGE/SELECT on all sequences, and matching `ALTER DEFAULT
  PRIVILEGES` so future tables/sequences created by the owner during migrations
  are covered automatically. `CREATE` on `public` is additionally revoked from
  the PUBLIC pseudo-role.

**How to apply.**
- Fresh volume (`docker compose up` with no existing data): the postgres service
  mounts `scripts/db-permissions.sql` into
  `/docker-entrypoint-initdb.d/10-db-permissions.sql`; the image runs it once as
  `POSTGRES_USER` (superuser) before the app starts.
- Existing database (e.g. the local dev DB): `node scripts/apply-db-permissions.mjs`
  — connects with the owner URL from `.env.local`, executes the same SQL, safe
  to re-run. Set `APP_DATABASE_PASSWORD` to override the documented default
  password (also rotates it on an existing role).

**How to verify.** `node scripts/verify-db-permissions.mjs` connects both as the
owner and as `receipt_app` and asserts: no superuser/CREATEDB/CREATEROLE, CONNECT
+ schema USAGE, DML on all tables/sequences, no schema CREATE, no DDL (a real
`CREATE TABLE` attempt is denied), default privileges are in place, and the owner
can still run DDL. Prints PASS/FAIL and exits 1 on failure.

**Limitation.** The runtime role has no DDL, so migrations and schema init must
use the owner URL. In production: run migrations with `DATABASE_URL` set to the
owner URL, then run the app with `DATABASE_URL` (or `APP_DATABASE_URL`, plumbed
through docker-compose) set to the restricted `receipt_app` URL.

