# ContaFlow Architecture Foundation

This document describes the current project foundation and where future features should be implemented.

## Backend Structure

- `app/Http/Controllers`
  - Controllers stay thin and only handle HTTP concerns (request/response, route-to-action wiring).
  - Added separate dashboard controllers:
    - `Admin/DashboardController`
    - `Client/DashboardController`
    - `DashboardRedirectController`
- `app/Actions`
  - Use actions for focused use-cases.
  - Example: `Dashboard/BuildDashboardDataAction` builds page-ready dashboard payload.
- `app/Services`
  - Use services for reusable application-level behavior.
  - Examples:
    - `Navigation/DashboardRouteResolver` resolves the target dashboard route per role.
    - `Logging/ActivityLogService` wraps manual audit logging and timeline queries.
- `app/Data`
  - DTO-style immutable classes for structured data.
  - Example: `Dashboard/DashboardPageData`.
- `app/Policies`
  - `UserPolicy` demonstrates policy-based authorization for user-centric permissions.
- `app/Http/Middleware`
  - Role/permission middleware aliases use Spatie:
    - `role`
    - `permission`
    - `role_or_permission`

## Roles and Permissions (Spatie)

- Package: `spatie/laravel-permission`
- User model uses `Spatie\Permission\Traits\HasRoles`.
- Roles are stored in Spatie tables (no custom `users.role` logic).
- Initial roles:
  - `admin`
  - `accountant`
  - `client`
- Initial permissions:
  - `view documents`
  - `upload documents`
  - `review documents`
  - `approve documents`
  - `reject documents`
  - `delete documents`
  - `manage users`
- Role assignment:
  - `admin` -> all permissions
  - `accountant` -> `view documents`, `review documents`, `approve documents`, `reject documents`
  - `client` -> `upload documents`, `view documents`
- Seeder entrypoint:
  - `database/seeders/RolesAndPermissionsSeeder.php`

## Activity and Audit Logging (Spatie)

- Package: `spatie/laravel-activitylog`
- Config and migration are published with package defaults.
- Automatic model audit logging:
  - `User` model uses `Spatie\Activitylog\Models\Concerns\LogsActivity`.
  - Logged attributes are restricted to relevant fields (`name`, `email`).
  - Log name is scoped per model (`users`).
  - Only dirty changes are stored to reduce noise.
- Manual domain/audit logging:
  - Use `app/Services/Logging/ActivityLogService`.
  - Methods:
    - `logCreated(model, user)`
    - `logUpdated(model, user)`
    - `logDeleted(model, user)`
    - `logCustom(description, subject, causer, properties)`
  - Example manual usage:
    - `ProfileController` logs `profile-updated` with changed attributes.
    - `ProfileController` logs `profile-deletion-requested` before delete.
- Stored activity fields:
  - `causer` (user)
  - `subject` (model instance)
  - `description` / `event`
  - `properties` (structured metadata)
- Per-entity timeline querying:
  - `ActivityLogService::getTimelineForSubject($model, $limit)` returns subject-scoped logs.
  - This is the baseline for future per-document timelines.

## Frontend Structure

- `resources/js/layouts`
  - Uses starter-kit layouts and sidebar structure.
- `resources/js/components`
  - Generic UI remains in `components/ui` (shadcn/ui style).
  - `app-sidebar.tsx` filters navigation items by permissions.
- `resources/js/pages`
  - `landing.tsx` for public entry.
  - `admin/dashboard.tsx` and `client/dashboard.tsx` for role-specific dashboards.
  - Dashboards currently render sample audit timeline entries from Inertia props.
- `resources/js/hooks`
  - `use-authorization.ts` exposes:
    - `can(permission)`
    - `hasRole(role)`
- `resources/js/types`
  - Shared typed contracts include:
    - user `roles` and `permissions`
    - activity log item types (`activity.ts`)

## Business Logic Placement

- Controllers: HTTP-only (validation orchestration, response shaping, redirects).
- Actions: single use-case operations (e.g. preparing dashboard data, orchestration steps).
- Services: reusable logic shared across actions/controllers.
- Models: represent domain entities and persistence concerns.
- Data classes: typed payload boundaries between backend layers and UI.
- Authorization:
  - Role checks: in services/routes where needed (`hasRole`, `hasAnyRole`).
  - Permission checks: route middleware (`permission:...`) and policies (`$user->can(...)`).
- Activity logging:
  - Automatic logs are for model lifecycle changes.
  - Manual logs are for important domain actions that are not plain CRUD.
  - Do not log every interaction; focus on traceable business-relevant events.

## Current Routing and Access Model

- Public:
  - `/` -> `landing` page
- Authenticated + verified:
  - `/dashboard` redirects to role-specific dashboard
  - `/admin/dashboard` protected by `role:admin|accountant` + `permission:review documents`
  - `/client/dashboard` protected by `role:client` + `permission:view documents`

## Backend and Frontend Authorization Checks

- Backend role check example:
  - `DashboardRouteResolver` resolves admin/client area by checking assigned roles.
- Backend permission check examples:
  - Route middleware: `permission:review documents`
  - Policy: `UserPolicy` uses `$user->can('manage users')`
- Frontend checks:
  - Shared props include `auth.user.roles` and `auth.user.permissions`.
  - `useAuthorization()` provides `can(permission)` and `hasRole(role)`.

## Backend and Frontend Activity Log Usage

- Backend querying:
  - Global: `ActivityLogService::getGlobalTimeline($limit)`
  - Per subject/model: `ActivityLogService::getTimelineForSubject($model, $limit)`
- Frontend usage:
  - Dashboard pages receive `activity` payload via Inertia.
  - UI consumes typed `ActivityLogItem` entries for timeline rendering.

## User Management Module

- Backend entrypoint:
  - `App\Http\Controllers\Admin\UserController` (HTTP-only orchestration)
- Business logic:
  - `App\Actions\Users\ListUsersAction`
  - `App\Actions\Users\CreateUserAction`
  - `App\Actions\Users\UpdateUserAction`
  - `App\Actions\Users\DeactivateUserAction`
  - `App\Actions\Users\ReactivateUserAction`
  - `App\Services\Users\UserAccessService` (roles/direct permissions sync)
- Validation:
  - Form requests under `app/Http/Requests/Admin/Users`
- Authorization:
  - Admin-only route group + Spatie permission middleware
  - Policy checks through `UserPolicy`
- User lifecycle:
  - No hard delete route for users
  - Explicit `is_active` flag is used for deactivate/reactivate flows
  - Inactive users are blocked from authentication
- Frontend organization:
  - `resources/js/pages/admin/users/*` for pages
  - `resources/js/features/users/components/*` for reusable filters/status UI
  - `resources/js/features/users/table/*` for table/columns/actions
  - `resources/js/features/users/forms/user-form.tsx` shared create/edit form
  - `resources/js/features/users/lib/*` for query-state helpers
  - `resources/js/features/users/types/*` for typed contracts
- Roles and permissions assignment:
  - Roles and direct permissions are synced separately
  - UI shows direct permissions and marks inherited ones for clarity
  - Changes are logged with activity entries (`roles-changed`, `permissions-changed`)

## Client Domain Model

- `User` vs `Client` separation:
  - `User` is an authenticated person (admin, accountant, client portal user).
  - `Client` is the accounting business entity (company/individual fiscal record) that documents and work will belong to.
  - This avoids mixing authentication concerns with fiscal/commercial identity data.
- `Client` structured fiscal/commercial fields:
  - `clients` stores explicit accounting identity data (`name`, `cif`, `trade_register_number`, location and address fields, VAT and legal-type flags).
  - `is_individual`, `is_vat_payer`, and `active` are explicit booleans for deterministic business behavior.
  - `active` defaults to `true` and supports deactivate/reactivate without hard delete.
- Contact persons are relational, not JSON:
  - `client_contacts` stores zero/one/many contacts per client.
  - Contacts are modeled as first-class entities to keep validation, querying, and future UI flows simple and performant.
  - `Client::primaryContact()` provides a clean helper for a designated primary contact.
- Client-user and accountant-client relationships:
  - Client portal users use `users.client_id` (one client can have multiple portal users).
  - Accountant assignments use `accountant_client` pivot (many-to-many) for scalable workload assignment.
  - This keeps business ownership and operational assignment separate and explicit.
- Validation and domain orchestration:
  - Form requests under `app/Http/Requests/Admin/Clients` enforce conditional rules (`cif` and `trade_register_number` required for companies, nullable for individuals).
  - Client actions under `app/Actions/Clients` handle status changes and contact upsert/remove flows.
  - `ClientAssignmentService` centralizes client portal-user and accountant sync logic.
- Activity logging:
  - Client model changes (`created`, `updated`) use model-level `LogsActivity` with limited attributes.
  - Domain events are logged explicitly for:
    - `client-deactivated`
    - `client-reactivated`
    - `client-contact-created`
    - `client-contact-updated`
    - `client-contact-removed`
    - `client-access-updated` (assignment changes)

## Document Domain Model

- Core entities:
  - `Document` is the main accounting workflow entity linked to a business client.
  - `DocumentFile` stores physical storage metadata (`disk`, `path`, filenames, mime type, size, checksum).
  - `DocumentExtraction` stores AI extraction attempts/results over time.
- Why `DocumentFile` is separated from `Document`:
  - Keeps storage concerns isolated from business workflow state.
  - Supports future multi-file/versioned attachments without changing the document aggregate shape.
  - Makes file lifecycle concerns (checksums, mime metadata, previews) extensible.
- Why `DocumentExtraction` is separated:
  - Supports multiple historical attempts per document (provider/model evolution, retries, audits).
  - Keeps raw provider payload (`raw_response_json`) separate from normalized app-ready data (`extracted_data_json`).
  - Enables future processing analytics and error tracking without polluting the main document row.
- Status modeling:
  - `DocumentStatus` enum: `uploaded`, `processing`, `processed`, `in_review`, `validated`, `pending_info`, `error`, `rejected`.
  - `ExtractionStatus` enum: `pending`, `processing`, `success`, `failed`.
- Key relationships:
  - `Document` belongs to `Client`.
  - `Document` belongs to uploader (`uploaded_by_user_id`) and optional reviewer (`reviewed_by_user_id`).
  - `Document` has many `DocumentFile` rows and many `DocumentExtraction` rows.
  - `Document::latestExtraction()` provides quick access to the latest extraction attempt.
- Helpers and scopes:
  - `Document` scopes: `pendingReview`, `validated`, `errored`.
  - Status helpers: `isValidated()`, `isRejected()`, `isPendingReview()`.
  - `DocumentExtraction` scopes: `pending`, `successful`, `failed`.
- Future readiness:
  - Upload flows can create `Document` + `DocumentFile` records with clean separation.
  - AI processing can append extraction attempts without overwriting prior runs.
  - Accountant review can evolve through explicit status transitions and reviewer timestamps.
  - Activity logging already captures meaningful state changes while avoiding noisy fields.

## Client Document Upload Flow

- Access rules:
  - Upload endpoint is exposed in the client portal (`/client/documents/upload`).
  - Only authenticated users with role `client` and permission `upload documents` can use this flow.
  - Policy guard: `DocumentPolicy::uploadFromClient` ensures the user can upload only for their own active client.
- Thin controller and business logic placement:
  - Controller: `app/Http/Controllers/Client/DocumentUploadController.php`
  - Request validation: `app/Http/Requests/Client/Documents/UploadClientDocumentRequest.php`
  - Use-case action: `app/Actions/Documents/UploadClientDocumentAction.php`
  - Storage concern: `app/Services/Documents/PublicClientDocumentStorageService.php`
- Storage strategy (public local folder):
  - Files are saved under `public/uploads/clients/{client_id}/documents/{Y}/{m}/...`
  - Stored filename is generated safely (`timestamp + uuid + extension`).
  - Original client filename is preserved in `document_files.original_filename`.
  - Metadata persisted in `document_files`: `disk`, `path`, `stored_filename`, `original_filename`, `mime_type`, `size`, `checksum`.
- Workflow result:
  - Creates `documents` row with status `uploaded`.
  - Creates `document_files` metadata row for the uploaded file.
  - Logs `document-uploaded` event with uploader, client, and file metadata.
- Future AI preparation:
  - Upload flow intentionally stops at persisted document + file.
  - AI extraction can be added later as asynchronous `document_extractions` creation without changing upload boundaries.

## Integration Settings and OpenAI Key Security

- Persistence model:
  - Application-level integration settings are stored in `app_settings`.
  - Structure is intentionally extensible with key-value records (`key`, `encrypted_value`, `metadata`).
  - Current OpenAI key entry uses key `integrations.openai.api_key`.
- Security model:
  - Sensitive values are encrypted at rest through Laravel encrypted casts (`AppSetting::encrypted_value`).
  - Raw OpenAI key is never sent to Inertia props or frontend state.
  - Frontend receives only safe state (`is_configured`, optional masked hint, `updated_at`).
- Backend structure:
  - Controller: `App\Http\Controllers\Admin\OpenAiSettingsController` (HTTP-only orchestration).
  - Validation: `App\Http\Requests\Admin\Integrations\SaveOpenAiApiKeyRequest`.
  - Actions:
    - `App\Actions\Integrations\SaveOpenAiApiKeyAction`
    - `App\Actions\Integrations\ClearOpenAiApiKeyAction`
  - Retrieval service for future OpenAI calls:
    - `App\Services\Integrations\OpenAiSettingsService::getApiKeyOrFail()`
    - Throws `App\Exceptions\MissingOpenAiApiKeyException` when key is missing.
- Authorization:
  - Access is restricted by role/permission middleware to admin users with `manage openai settings`.
  - Intended for backend-only API usage by future document processing services.
- Audit logging:
  - Logs safe events only:
    - `openai-key-configured`
    - `openai-key-updated`
    - `openai-key-cleared`
  - Logs never include raw credential values.

## AI Document Processing Layer (MVP)

- Entry point:
  - Core orchestration lives in `App\Actions\Documents\ProcessDocumentExtractionAction`.
  - Action accepts a `Document`, resolves latest stored file, updates statuses, persists extraction attempts, and logs processing events.
- OpenAI integration isolation:
  - `App\Services\Documents\OpenAiDocumentExtractionService` encapsulates all OpenAI HTTP requests.
  - Service reads API key through `App\Services\Integrations\OpenAiSettingsService`.
  - No controller or UI layer accesses provider APIs or credentials directly.
- Status flow:
  - `uploaded` -> `processing` when processing starts.
  - Success:
    - `document_extractions` entry with `status=success`, provider/model/raw/normalized payload.
    - document status becomes `processed`, `processed_at` is set.
  - Failure:
    - `document_extractions` entry with `status=failed` and safe `error_message`.
    - document status becomes `error`.
- Extraction schema (first version):
  - Normalized JSON includes:
    - `document_type`, `issuer_name`, `issuer_identifier`, `document_number`
    - `issue_date`, `due_date`, `currency`
    - `total_amount`, `vat_amount`
    - `line_items`
    - `notes`
    - optional `confidence`
  - Raw provider response is stored separately from normalized extracted data.
- Triggering (minimal):
  - Admin/accountant web trigger route: `POST /admin/documents/{document}/parse`
  - Artisan trigger for manual testing: `php artisan documents:process {documentId}`
  - Permission guard: `process documents`
- Activity logging:
  - `document-processing-started`
  - `document-processed-successfully`
  - `document-processing-failed`
  - Logs include safe metadata only, never credentials.
- Future OCR fallback compatibility:
  - OCR is intentionally not implemented yet.
  - The extraction orchestration is centralized, so an OCR-first/preprocessing branch can be inserted before provider extraction without changing controllers/routes or extraction persistence shape.

## Admin Documents Index and Parse Trigger

- Admin listing entrypoint:
  - Controller: `App\Http\Controllers\Admin\DocumentController@index` (HTTP-only orchestration)
  - Action: `App\Actions\Documents\ListDocumentsAction`
  - Query abstraction: `App\Queries\Documents\DocumentIndexQuery`
  - Request validation: `App\Http\Requests\Admin\Documents\DocumentIndexRequest`
  - DTO shaping: `App\Data\Documents\DocumentListItemData`
- Server-side filters and URL sync:
  - Filters include `client_id`, `status`, `extraction_status`, `uploaded_by_user_id`, date range (`uploaded_from`, `uploaded_to`) and search.
  - Sort/pagination/filter state is kept in query params and preserved through table navigation.
  - Frontend state sync lives in `resources/js/features/documents/lib/use-documents-filters.ts`.
- Frontend table modularity:
  - Page: `resources/js/pages/admin/documents/index.tsx`
  - Table core: `resources/js/features/documents/table/documents-table.tsx`
  - Columns: `resources/js/features/documents/table/documents-columns.tsx`
  - Row action + parse button:
    - `document-row-actions.tsx`
    - `parse-document-button.tsx`
  - Status badges:
    - `document-status-badge.tsx`
    - `extraction-status-badge.tsx`
- Parse trigger flow:
  - Frontend calls internal endpoint `POST /admin/documents/{document}/parse`.
  - Frontend never re-uploads files and never calls OpenAI directly.
  - Backend reuses persisted file metadata/path and delegates processing to `ProcessDocumentExtractionAction`.
- Authorization:
  - List requires `view documents`.
  - Parse trigger requires `process documents`.
  - UI hides parse button for users without processing permission.

## Admin Document Detail Modal

- Detail loading pattern:
  - The documents index stays the single Inertia page entrypoint (`DocumentController@index`).
  - Modal state is synchronized with query param `document_id`, so links are shareable/bookmarkable.
  - Existing table filters/sort/pagination remain in URL and are preserved while opening/closing the modal.
- Backend payload shaping:
  - Detail payload is built through `App\Data\Documents\DocumentDetailData` (not raw model dumps).
  - Data includes document metadata, client context, uploader, latest file metadata, preview URL, latest extraction snapshots, internal comments, and a shaped activity timeline.
  - Activity items are normalized through `App\Data\Documents\DocumentActivityItemData` and include only UI-relevant fields (`label`, `type`, `causer_name`, `created_at`, safe metadata chips).
  - Discussion comments are normalized through `App\Data\Documents\DocumentCommentData` and include only list/render fields (`author_name`, `body`, `created_at`, internal flag).
  - `ListDocumentsAction::detailForModal()` isolates document detail retrieval and relation loading.
- Frontend modular composition:
  - Container modal: `document-detail-modal.tsx`
  - Metadata header: `document-metadata-header.tsx`
  - File preview panel: `document-preview-panel.tsx`
  - Right workspace tabs: `document-detail-tabs.tsx`
    - `Extrase`: `document-extraction-tab.tsx` + `document-extraction-panel.tsx`
    - `Activitate`: `document-activity-tab.tsx` + `activity-timeline.tsx` + `activity-timeline-item.tsx`
    - `Discutii`: `document-comments-tab.tsx` + `document-comments-list.tsx` + `document-comment-item.tsx` + `add-document-comment-form.tsx`
  - This keeps page-level wiring in `pages/admin/documents/index.tsx` and avoids giant components.
- Right panel responsibility split:
  - `Extrase` tab is dedicated to extracted accounting data presentation and optional raw JSON debug.
  - `Activitate` tab is dedicated to system/audit timeline events for the document.
  - `Discutii` tab is dedicated to human collaboration comments (flat list + composer), stored in `document_comments` and intentionally separate from activity/audit logs.
  - This separation keeps future extensions straightforward (mentions, client-visible discussions, reply threads) without mixing audit and conversation concerns.
- File preview strategy:
  - `application/pdf` uses embedded iframe preview.
  - `image/*` is rendered directly.
  - Unsupported MIME types show a fallback card with file metadata and open/download action.
- Extraction presentation:
  - Right panel renders accountant-friendly key sections from `extracted_data_json`.
  - Missing fields are handled gracefully with placeholders.
  - Raw JSON remains optional behind a collapsible debug section.

## Document-Centric Documents Table

- Row design:
  - Documents index keeps TanStack Table but uses a document-list visual structure instead of a technical CRUD grid.
  - Main columns are focused on scanning: expand control, document identity, derived type, size, status, actions.
  - Technical metadata (for example MIME type) is intentionally demoted from primary table columns.
- List-specific backend shaping:
  - `DocumentListItemData` now derives list-ready fields from latest successful extraction:
    - `document_type` (`key` + human label)
    - `summary` (issuer, document number, issue/due dates, total, VAT)
  - `ListDocumentsAction` eagerly loads `latestSuccessfulExtraction` so table rows do not parse raw JSON in frontend cells.
- Expandable summary rows:
  - Parsed rows can be expanded inline with a chevron control.
  - Expanded content renders a compact accounting summary card, not raw extraction JSON.
  - Missing values are rendered with safe placeholders to keep the layout stable.
- Frontend modular components:
  - Primary document cell: `document-primary-cell.tsx`
  - Type label/chip: `document-type-label.tsx`
  - Type icon/appearance mapping: `lib/document-type.tsx`
  - Expanded row summary: `expanded-document-summary.tsx`
  - Table wiring remains in `documents-table.tsx` / `documents-columns.tsx`.

## Document Review Workflow

- Status data vs workflow responsibilities:
  - `documents.status` remains the single source of truth for current operational state.
  - Review workflow is a controlled transition layer over that status, not a second status system.
  - Human discussion stays in `document_comments`; workflow reasons are stored separately and never mixed with discussions.
- Transition rules location:
  - Centralized in `App\Services\Documents\DocumentReviewTransitionService`.
  - Service defines:
    - allowed actions per current status
    - required-reason actions
    - target status resolution for each action
  - This avoids arbitrary status mutation from controllers or random forms.
- Backend review action orchestration:
  - Endpoint: `POST /admin/documents/{document}/review`.
  - Request validation/authorization: `App\Http\Requests\Admin\Documents\PerformDocumentReviewRequest`.
  - Use-case action: `App\Actions\Documents\PerformDocumentReviewAction`.
  - Policy gate: `DocumentPolicy::performReviewAction`.
  - Controllers remain HTTP-only and delegate business logic.
- Workflow reasons storage:
  - Dedicated table: `document_review_actions`.
  - Each entry stores: actor, action, from status, to status, optional/required reason, timestamp.
  - This keeps workflow audit intent explicit and separate from collaboration comments.
- Supported review actions:
  - `mark_in_review`
  - `validate`
  - `set_pending_info` (reason required)
  - `mark_error` (reason required)
  - `reject` (reason required)
  - `reprocess` (re-triggers AI parsing flow)
- UI exposure:
  - Document detail modal uses `DocumentReviewActions` for accountant/admin operational controls.
  - Actions that require a reason open a focused dialog with textarea input.
  - The documents table keeps quick status actions for safe transitions and refreshes via Inertia after review operations.
- Activity log behavior:
  - Manual review events are logged via `ActivityLogService` with:
    - actor (`causer`)
    - previous/new status
    - reason (when present)
  - Events are visible in the `Activitate` tab through `DocumentActivityItemData`.
  - Review workflow emits meaningful domain events without reusing discussion comment records.

## Extraction JSON vs Structured Accounting Data

- Two persistence layers are intentionally kept:
  - `document_extractions.extracted_data_json` stores the raw/normalized extraction payload history per attempt.
  - Relational accounting tables store queryable business data used by operational workflows and reporting.
- Structured relational tables:
  - `document_accounting_data` (one row per document, upserted)
  - `document_accounting_line_items` (many rows per document accounting record, recreated per persistence run)
- Mapping and persistence strategy:
  - Centralized in `App\Actions\Documents\PersistDocumentAccountingDataAction`.
  - Action receives a `Document` and a successful `DocumentExtraction`, normalizes payload, then:
    - upserts header-level accounting fields
    - deletes existing line items and recreates current mapped line items
  - Missing/invalid fields are handled defensively (partial persistence, no hard crash).
- Why relational storage is required:
  - JSON-only extraction payload is useful for audit/debug, but not enough for reliable filtering, joins, aggregates, and downstream accounting operations.
  - Relational tables provide deterministic schema for core document accounting fields and line-level calculations.
- Pipeline integration:
  - Extraction flow after successful OpenAI parsing:
    - save `document_extractions` history
    - persist/update structured accounting data
  - Manual extracted-data edits also trigger structured persistence to keep JSON and relational state aligned.
- Activity logs:
  - `document-structured-data-persisted`
  - `document-structured-data-updated`

## Mailbox Integration (IMAP) - Settings vs Ingestion

- Scope:
  - This module focuses on inbox reading via IMAP and ingestion preparation (email metadata + attachments).
  - Outgoing email sending is intentionally out of scope for this phase.
- Why IMAP:
  - IMAP provides standardized access to mailboxes (folders, message metadata, attachments).
  - This is needed for pulling inbound invoices/receipts from email accounts.
- Credential storage:
  - `mailbox_accounts.password` is stored encrypted at rest using Laravel encrypted casts.
  - Passwords are never sent to Inertia props or shown in UI.
  - Edit form allows setting a new password without revealing the current one.
- Architecture:
  - Admin UI:
    - Page: `resources/js/pages/admin/settings/mailboxes/index.tsx`
    - Form: `resources/js/features/mailboxes/forms/mailbox-account-form.tsx`
  - Backend:
    - Controller: `App\Http\Controllers\Admin\MailboxAccountController` (thin HTTP orchestration)
    - Validation: `app/Http/Requests/Admin/Mailboxes/*`
    - Actions: `app/Actions/Mailboxes/*`
  - IMAP isolation:
    - `App\Services\Mailboxes\MailboxConnectionService` encapsulates IMAP connection + message scanning.
    - Controllers never call IMAP functions directly.
  - Ingestion skeleton:
    - `App\Services\Mailboxes\MailboxIngestionService` performs a safe sync:
      - reads recent messages
      - persists message + attachment metadata
      - creates `mailbox_sync_runs` entries for auditing
    - `SyncMailboxAccountJob` runs the sync in the background.
- Future attachment import preparation:
  - `mailbox_messages` stores `message_id` per mailbox to allow duplicate protection later.
  - `mailbox_message_attachments.checksum` is stored to support attachment-level de-duplication.
  - Next step can reuse existing document upload flow for storage and then link created `documents` to mailbox sources.
- Activity logs:
  - Configuration: `mailbox-account-created/updated/activated/deactivated`
  - Testing: `mailbox-connection-tested`
  - Sync: `mailbox-sync-dispatched/started/completed/failed`

## Breadcrumb Strategy

- `AppLayout` resolves breadcrumbs dynamically when page-level breadcrumbs are not provided.
- The resolver lives in `resources/js/lib/breadcrumbs.ts`.
- This keeps admin pages (`Admin Dashboard`, `Users`, `Create User`, `Edit User`) consistent without duplicating breadcrumb configuration in each page.

## Reusable Data Table Filtering Pattern

- Frontend reusable building blocks:
  - `resources/js/components/data-table/data-table-toolbar.tsx`
  - `resources/js/components/data-table/data-table-filters-sheet.tsx`
  - `resources/js/components/data-table/filter-rule-row.tsx`
  - `resources/js/components/data-table/active-filter-badges.tsx`
  - `resources/js/components/data-table/types.ts`
- This pattern keeps column headers clean and moves advanced filtering into one controlled sheet.
- Feature modules provide only configuration and wiring:
  - `resources/js/features/clients/lib/filter-fields.ts`
  - `resources/js/features/clients/lib/query-state.ts`
  - `resources/js/features/clients/lib/use-clients-filters.ts`
  - `resources/js/features/clients/table/*`
- URL sync strategy:
  - Search, sort, pagination, and advanced filter rules are stored in query params.
  - Navigation and reload keep the exact table state.
  - Inertia `router.get(..., { replace: true })` avoids noisy history entries while users refine filters.
- Backend server-side filtering:
  - Request validation: `app/Http/Requests/Admin/Clients/ClientIndexRequest.php`
  - Filter DTOs: `app/Data/Clients/ClientIndexFiltersData.php`, `ClientFilterRuleData.php`
  - Query abstraction: `app/Queries/Clients/ClientIndexQuery.php`
  - Action orchestration: `app/Actions/Clients/ListClientsAction.php`
  - Controller remains HTTP-only and delegates filtering logic to action/query classes.

## Workflow Extension Guidelines

For future extensions of the existing review flow:

1. Extend `DocumentReviewTransitionService` first (single transition source).
2. Keep new operation endpoints thin and route them through dedicated actions.
3. Keep reason-like operational data in workflow records, not in discussion comments.
4. Add activity events only for meaningful domain steps.
5. Keep UI actions in `resources/js/features/documents/components` and avoid page-level business logic.
