commit 35545c2a8f603c5d733380e1dca6f4fbc8d3254f Author: Saad Ibn-Ezzoubayr Date: Wed Mar 11 23:33:10 2026 +0000 feat: L'Ami Fiduciaire V1.0.0 — full codebase with Story 0.1 complete Initial commit of the L'Ami Fiduciaire SaaS platform built on Laravel 12, Vue 3, Inertia.js 2, and Tailwind CSS 4. Story 0.1 (rename folders to declarations in database) is implemented and code-reviewed: migration, rollback, and 6 Pest tests all passing. Co-Authored-By: Claude Opus 4.6 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a186cd2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[compose.yaml] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3a15d54 --- /dev/null +++ b/.env.example @@ -0,0 +1,65 @@ +APP_NAME="Fiscal Calendar" +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +APP_LOCALE=fr +APP_FALLBACK_LOCALE=fr +APP_FAKER_LOCALE=fr_FR + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +# PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_DATABASE=laravel +# DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=databasew +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4da48a8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +CHANGELOG.md export-ignore +README.md export-ignore +.github/workflows/browser-tests.yml export-ignore diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..619061f --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,49 @@ +name: linter + +on: + push: + branches: + - develop + - main + - master + - workos + pull_request: + branches: + - develop + - main + - master + - workos + +permissions: + contents: write + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + + - name: Install Dependencies + run: | + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + npm install + + - name: Run Pint + run: composer lint + + - name: Format Frontend + run: npm run format + + - name: Lint Frontend + run: npm run lint + + # - name: Commit Changes + # uses: stefanzweifel/git-auto-commit-action@v7 + # with: + # commit_message: fix code style + # commit_options: '--no-verify' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b2ded27 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,56 @@ +name: tests + +on: + push: + branches: + - develop + - main + - master + - workos + pull_request: + branches: + - develop + - main + - master + - workos + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: ['8.4', '8.5'] + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer:v2 + coverage: xdebug + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install Node Dependencies + run: npm i + + - name: Install Dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Copy Environment File + run: cp .env.example .env + + - name: Generate Application Key + run: php artisan key:generate + + - name: Build Assets + run: npm run build + + - name: Tests + run: ./vendor/bin/pest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcf288b --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +/.phpunit.cache +/bootstrap/ssr +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/resources/js/actions +/resources/js/routes +/resources/js/wayfinder +/vendor +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log +/auth.json +/.fleet +/.idea +/.nova +/.vscode +/.zed +/.claude +/.cursor +/.windsurf diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..2a2592c --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +public-hoist-pattern[]=@inertiajs/core diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6b929ae --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +resources/js/components/ui/* +resources/views/mail/* diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..92805fa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,25 @@ +{ + "semi": true, + "singleQuote": true, + "singleAttributePerLine": false, + "htmlWhitespaceSensitivity": "css", + "printWidth": 80, + "plugins": [ + "prettier-plugin-tailwindcss" + ], + "tailwindFunctions": [ + "clsx", + "cn", + "cva" + ], + "tailwindStylesheet": "resources/css/app.css", + "tabWidth": 4, + "overrides": [ + { + "files": "**/*.yml", + "options": { + "tabWidth": 2 + } + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..779d117 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,314 @@ + +=== foundation rules === + +# Laravel Boost Guidelines + +The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to ensure the best experience when building Laravel applications. + +## Foundational Context + +This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. + +- php - 8.4.8 +- inertiajs/inertia-laravel (INERTIA_LARAVEL) - v2 +- laravel/fortify (FORTIFY) - v1 +- laravel/framework (LARAVEL) - v12 +- laravel/prompts (PROMPTS) - v0 +- laravel/wayfinder (WAYFINDER) - v0 +- laravel/boost (BOOST) - v2 +- laravel/mcp (MCP) - v0 +- laravel/pail (PAIL) - v1 +- laravel/pint (PINT) - v1 +- laravel/sail (SAIL) - v1 +- pestphp/pest (PEST) - v4 +- phpunit/phpunit (PHPUNIT) - v12 +- @inertiajs/vue3 (INERTIA_VUE) - v2 +- tailwindcss (TAILWINDCSS) - v4 +- vue (VUE) - v3 +- @laravel/vite-plugin-wayfinder (WAYFINDER_VITE) - v0 +- eslint (ESLINT) - v9 +- prettier (PRETTIER) - v3 + +## Skills Activation + +This project has domain-specific skills available. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck. + +- `wayfinder-development` — Activates whenever referencing backend routes in frontend components. Use when importing from @/actions or @/routes, calling Laravel routes from TypeScript, or working with Wayfinder route functions. +- `pest-testing` — Tests applications using the Pest 4 PHP framework. Activates when writing tests, creating unit or feature tests, adding assertions, testing Livewire components, browser testing, debugging test failures, working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion, coverage, or needs to verify functionality works. +- `inertia-vue-development` — Develops Inertia.js v2 Vue client-side applications. Activates when creating Vue pages, forms, or navigation; using <Link>, <Form>, useForm, or router; working with deferred props, prefetching, or polling; or when user mentions Vue with Inertia, Vue pages, Vue forms, or Vue navigation. +- `tailwindcss-development` — Styles applications using Tailwind CSS v4 utilities. Activates when adding styles, restyling components, working with gradients, spacing, layout, flex, grid, responsive design, dark mode, colors, typography, or borders; or when the user mentions CSS, styling, classes, Tailwind, restyle, hero section, cards, buttons, or any visual/UI changes. +- `developing-with-fortify` — Laravel Fortify headless authentication backend development. Activate when implementing authentication features including login, registration, password reset, email verification, two-factor authentication (2FA/TOTP), profile updates, headless auth, authentication scaffolding, or auth guards in Laravel applications. + +## Conventions + +- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming. +- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. +- Check for existing components to reuse before writing a new one. + +## Verification Scripts + +- Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important. + +## Application Structure & Architecture + +- Stick to existing directory structure; don't create new base folders without approval. +- Do not change the application's dependencies without approval. + +## Frontend Bundling + +- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail npm run build`, `vendor/bin/sail npm run dev`, or `vendor/bin/sail composer run dev`. Ask them. + +## Documentation Files + +- You must only create documentation files if explicitly requested by the user. + +## Replies + +- Be concise in your explanations - focus on what's important rather than explaining obvious details. + +=== boost rules === + +# Laravel Boost + +- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. + +## Artisan + +- Use the `list-artisan-commands` tool when you need to call an Artisan command to double-check the available parameters. + +## URLs + +- Whenever you share a project URL with the user, you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain/IP, and port. + +## Tinker / Debugging + +- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. +- Use the `database-query` tool when you only need to read from the database. +- Use the `database-schema` tool to inspect table structure before writing migrations or models. + +## Reading Browser Logs With the `browser-logs` Tool + +- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost. +- Only recent browser logs will be useful - ignore old logs. + +## Searching Documentation (Critically Important) + +- Boost comes with a powerful `search-docs` tool you should use before trying other approaches when working with Laravel or Laravel ecosystem packages. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. +- Search the documentation before making code changes to ensure we are taking the correct approach. +- Use multiple, broad, simple, topic-based queries at once. For example: `['rate limiting', 'routing rate limiting', 'routing']`. The most relevant results will be returned first. +- Do not add package names to queries; package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. + +### Available Search Syntax + +1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'. +2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit". +3. Quoted Phrases (Exact Position) - query="infinite scroll" - words must be adjacent and in that order. +4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit". +5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms. + +=== php rules === + +# PHP + +- Always use curly braces for control structures, even for single-line bodies. + +## Constructors + +- Use PHP 8 constructor property promotion in `__construct()`. + - `public function __construct(public GitHub $github) { }` +- Do not allow empty `__construct()` methods with zero parameters unless the constructor is private. + +## Type Declarations + +- Always use explicit return type declarations for methods and functions. +- Use appropriate PHP type hints for method parameters. + + +```php +protected function isAccessible(User $user, ?string $path = null): bool +{ + ... +} +``` + +## Enums + +- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. + +## Comments + +- Prefer PHPDoc blocks over inline comments. Never use comments within the code itself unless the logic is exceptionally complex. + +## PHPDoc Blocks + +- Add useful array shape type definitions when appropriate. + +=== sail rules === + +# Laravel Sail + +- This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. +- Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. +- Open the application in the browser by running `vendor/bin/sail open`. +- Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples: + - Run Artisan Commands: `vendor/bin/sail artisan migrate` + - Install Composer packages: `vendor/bin/sail composer install` + - Execute Node commands: `vendor/bin/sail npm run dev` + - Execute PHP scripts: `vendor/bin/sail php [script]` +- View all available Sail commands by running `vendor/bin/sail` without arguments. + +=== tests rules === + +# Test Enforcement + +- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. +- Run the minimum number of tests needed to ensure code quality and speed. Use `vendor/bin/sail artisan test --compact` with a specific filename or filter. + +=== inertia-laravel/core rules === + +# Inertia + +- Inertia creates fully client-side rendered SPAs without modern SPA complexity, leveraging existing server-side patterns. +- Components live in `resources/js/pages` (unless specified in `vite.config.js`). Use `Inertia::render()` for server-side routing instead of Blade views. +- ALWAYS use `search-docs` tool for version-specific Inertia documentation and updated code examples. +- IMPORTANT: Activate `inertia-vue-development` when working with Inertia Vue client-side patterns. + +# Inertia v2 + +- Use all Inertia features from v1 and v2. Check the documentation before making changes to ensure the correct approach. +- New features: deferred props, infinite scroll, merging props, polling, prefetching, once props, flash data. +- When using deferred props, add an empty state with a pulsing or animated skeleton. + +=== laravel/core rules === + +# Do Things the Laravel Way + +- Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. +- If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. +- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. + +## Database + +- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. +- Use Eloquent models and relationships before suggesting raw database queries. +- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. +- Generate code that prevents N+1 query problems by using eager loading. +- Use Laravel's query builder for very complex database operations. + +### Model Creation + +- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. + +### APIs & Eloquent Resources + +- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. + +## Controllers & Validation + +- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. +- Check sibling Form Requests to see if the application uses array or string based validation rules. + +## Authentication & Authorization + +- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). + +## URL Generation + +- When generating links to other pages, prefer named routes and the `route()` function. + +## Queues + +- Use queued jobs for time-consuming operations with the `ShouldQueue` interface. + +## Configuration + +- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. + +## Testing + +- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. +- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. +- When creating tests, make use of `vendor/bin/sail artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. + +## Vite Error + +- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail npm run build` or ask the user to run `vendor/bin/sail npm run dev` or `vendor/bin/sail composer run dev`. + +=== laravel/v12 rules === + +# Laravel 12 + +- CRITICAL: ALWAYS use `search-docs` tool for version-specific Laravel documentation and updated code examples. +- Since Laravel 11, Laravel has a new streamlined file structure which this project uses. + +## Laravel 12 Structure + +- In Laravel 12, middleware are no longer registered in `app/Http/Kernel.php`. +- Middleware are configured declaratively in `bootstrap/app.php` using `Application::configure()->withMiddleware()`. +- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files. +- `bootstrap/providers.php` contains application specific service providers. +- The `app\Console\Kernel.php` file no longer exists; use `bootstrap/app.php` or `routes/console.php` for console configuration. +- Console commands in `app/Console/Commands/` are automatically available and do not require manual registration. + +## Database + +- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. +- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. + +### Models + +- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. + +=== wayfinder/core rules === + +# Laravel Wayfinder + +Wayfinder generates TypeScript functions for Laravel routes. Import from `@/actions/` (controllers) or `@/routes/` (named routes). + +- IMPORTANT: Activate `wayfinder-development` skill whenever referencing backend routes in frontend components. +- Invokable Controllers: `import StorePost from '@/actions/.../StorePostController'; StorePost()`. +- Parameter Binding: Detects route keys (`{post:slug}`) — `show({ slug: "my-post" })`. +- Query Merging: `show(1, { mergeQuery: { page: 2, sort: null } })` merges with current URL, `null` removes params. +- Inertia: Use `.form()` with `
` component or `form.submit(store())` with useForm. + +=== pint/core rules === + +# Laravel Pint Code Formatter + +- If you have modified any PHP files, you must run `vendor/bin/sail bin pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style. +- Do not run `vendor/bin/sail bin pint --test --format agent`, simply run `vendor/bin/sail bin pint --format agent` to fix any formatting issues. + +=== pest/core rules === + +## Pest + +- This project uses Pest for testing. Create tests: `vendor/bin/sail artisan make:test --pest {name}`. +- Run tests: `vendor/bin/sail artisan test --compact` or filter: `vendor/bin/sail artisan test --compact --filter=testName`. +- Do NOT delete tests without approval. +- CRITICAL: ALWAYS use `search-docs` tool for version-specific Pest documentation and updated code examples. +- IMPORTANT: Activate `pest-testing` every time you're working with a Pest or testing-related task. + +=== inertia-vue/core rules === + +# Inertia + Vue + +Vue components must have a single root element. +- IMPORTANT: Activate `inertia-vue-development` when working with Inertia Vue client-side patterns. + +=== tailwindcss/core rules === + +# Tailwind CSS + +- Always use existing Tailwind conventions; check project patterns before adding new ones. +- IMPORTANT: Always use `search-docs` tool for version-specific Tailwind CSS documentation and updated code examples. Never rely on training data. +- IMPORTANT: Activate `tailwindcss-development` every time you're working with a Tailwind CSS or styling-related task. + +=== laravel/fortify rules === + +# Laravel Fortify + +- Fortify is a headless authentication backend that provides authentication routes and controllers for Laravel applications. +- IMPORTANT: Always use the `search-docs` tool for detailed Laravel Fortify patterns and documentation. +- IMPORTANT: Activate `developing-with-fortify` skill when working with Fortify authentication features. + + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..922a9a0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,4 @@ +# Project Rules + +- Everything must run under Docker Compose — no local installations allowed. +- When implementing Laravel features, use the Laravel docs MCP to verify current API usage, especially for Eloquent, Inertia, Fortify, and Spatie packages. diff --git a/_bmad-output/brainstorming/brainstorming-session-2026-03-10-120000.md b/_bmad-output/brainstorming/brainstorming-session-2026-03-10-120000.md new file mode 100644 index 0000000..8baa085 --- /dev/null +++ b/_bmad-output/brainstorming/brainstorming-session-2026-03-10-120000.md @@ -0,0 +1,281 @@ +--- +stepsCompleted: [1, 2, 3, 4] +inputDocuments: [] +session_topic: 'SaaS dashboard separation + UX improvements + per-workspace archiving system' +session_goals: 'Clean multi-layer dashboard architecture, intuitive UX, robust archiving per workspace' +selected_approach: 'ai-recommended' +techniques_used: ['Role Playing', 'First Principles Thinking', 'SCAMPER Method', 'Morphological Analysis'] +ideas_generated: 39 +context_file: '' +session_active: false +workflow_completed: true +--- + +# Brainstorming Session Results + +**Facilitator:** Saad +**Date:** 2026-03-10 + +## Session Overview + +**Topic:** SaaS dashboard separation (owner vs tenant) + UX improvements + per-workspace archiving system +**Goals:** Generate ideas for a well-structured, intuitive platform with clear role boundaries and a robust archiving layer per workspace + +### Session Setup + +- Platform: L'Ami Fiduciaire — client management SaaS for accounting/fiduciary firms +- Current state: Mature brownfield app (Laravel 12 + Vue 3 + Inertia.js) with mixed admin/tenant dashboards +- Pain points: Role boundaries blurred, UX feels cluttered, no archiving system yet +- User types: SaaS owner (L'Ami Fiduciaire staff), firm admin, firm employee, external client + +### Design Philosophy + +- **عملي (practical) first, beauty second** — every UI decision must make the task faster, not slower +- Tables over cards for data views — accountants think in rows and columns +- One app, role-driven views — no separate admin app needed + +## Technique Selection + +**Approach:** AI-Recommended Techniques (Suggested Flow) +**Analysis Context:** Multi-layered SaaS UX problem requiring role clarity, interface refinement, and new feature design + +**Technique Flow:** + +1. **Role Playing** — Clarify what each user type actually needs to see and do +2. **First Principles Thinking** — Define the right dashboard structure from fundamental truths +3. **SCAMPER Method** — Refine and improve existing UX systematically +4. **Morphological Analysis** — Design the archiving system thoroughly across all dimensions + +--- + +## Technique Execution Results + +### Phase 1: Role Playing — User Persona Analysis + +Four distinct user layers identified with clear separation of needs: + +| Layer | Sees | Key Need | +|-------|------|----------| +| **SaaS Owner** (L'Ami Fiduciaire staff) | All workspaces, platform metrics, issues | "Is my platform healthy?" | +| **Firm Admin/Owner** | Their workspace — all clients, all declarations, alerts | "Is my firm on track? Who needs a nudge?" | +| **Firm Employee (Worker)** | Only assigned clients/declarations + notifications | "What do I need to do right now?" | +| **External Client** | Single declaration action via token link | "Upload this, confirm that, done." | + +**Key Insight:** The current problem is that layers 1, 2, and 3 share the same interface. The fix is role-driven visibility within a single app shell. + +### Phase 2: First Principles Thinking — Structure & Permissions + +**Core Decision: Fixed Roles + Permission Overrides** + +- Three preset roles: **Owner, Manager, Worker** +- Sensible defaults that work for 90% of firms out of the box +- Firm owner can toggle specific permissions on/off per role +- Permissions stored at workspace level — each cabinet customizes independently + +**Navigation Structure:** + +**SaaS Owner:** +- Platform Dashboard (metrics, health) +- Workspaces (manage all cabinets) +- Reported Issues + +**Firm Owner/Manager:** +- Dashboard (command center — alerts + stats combined) +- Clients +- Declarations +- Archive +- Team (users + permissions) + +**Worker:** +- My Dashboard (assigned work + nudges) +- My Clients +- My Declarations + +### Phase 3: SCAMPER — UX Refinements + +- **Substitute:** Nothing needed — current component choices are solid +- **Combine:** Dashboard + Alerts into one unified command center view +- **Adapt:** Advanced filtering system consistent across all views + search bars +- **Modify:** Inline tag/nudge on declaration rows — micro-interaction, no page navigation +- **Eliminate:** Separate admin routes — merge user management into workspace Team page +- **Reverse:** Client-initiated declarations — clients can request a declaration through portal (future) + +**Breakthrough:** Bulk Declaration Creation — select multiple clients, choose type, create all at once with scheduled notifications. + +### Phase 4: Morphological Analysis — Archive System Design + +| Dimension | Decision | +|-----------|----------| +| **What gets archived** | Declaration metadata, status history, all documents, messages, client portal actions | +| **Organization** | Hybrid — flat list with powerful filters (fiscal year, client, type, date range) + search bar | +| **Access** | Owner and Manager only — Worker excluded | +| **Mutability** | Read-only, but Owner/Manager can re-open (moves back to active) | +| **Audit** | Re-open logged with who, when, and why | +| **Retention** | 10 years from closing date, warning before cleanup | +| **Export** | Bulk download as ZIP + in-app document preview | +| **Search** | Metadata search first (client, type, year, date). Full-text document search in future phase | +| **Navigation** | Top-level sidebar item — first-class feature, not hidden | +| **Visual** | Muted styling / archive badge to distinguish from active declarations | + +--- + +## Complete Idea Inventory + +### Theme 1: Platform Architecture — Role Separation + +| # | Idea | Description | +|---|------|-------------| +| 1 | Platform Health Dashboard | SaaS owner dedicated view: workspace count, active users, storage, signups, system health | +| 2 | Issue/Support Inbox | Lightweight issue reporting — firm users flag problems, SaaS owner sees aggregated | +| 10 | Client Portal — Keep As-Is | External client one-shot token experience is solid, no changes needed | +| 11 | Single Shell, Role-Driven Views | One app, one sidebar, content determined by role — no separate admin app | +| 14 | Fixed Roles + Permission Matrix | Owner/Manager/Worker with sensible defaults + toggle overrides per workspace | +| 15 | Sensible Defaults That Just Work | 90% of firms never touch permissions — it just works out of the box | +| 16 | Per-Workspace Permission Storage | Each cabinet customizes permissions independently — true multi-tenancy | +| 28 | Eliminate Separate Admin Routes | Team management inside workspace, not through a separate admin panel | + +### Theme 2: Dashboard & Command Center + +| # | Idea | Description | +|---|------|-------------| +| 3 | Command Center Home | Single screen: active clients, declarations by status, activity feed, priority alerts | +| 4 | Priority Alert System | Visual alerts for overdue declarations, unanswered uploads, unsigned confirmations | +| 7 | Scoped Employee Dashboard | Workers see only assigned clients/declarations — "here's what you need to do today" | +| 8 | Notification Center for Nudges | Employee notification area where admin tags/alerts land with direct links to declarations | +| 19 | Dashboard = Alerts Combined | No separate alerts page — dashboard IS the alert system, one unified view | + +### Theme 3: Firm Management & Collaboration + +| # | Idea | Description | +|---|------|-------------| +| 6 | Quick Tag & Nudge | One-click alert from admin to employee on any declaration — tap on the shoulder, not a ticket | +| 12 | Firm-Level Role Management Page | Dedicated "Team" page to create users with roles (Owner, Manager, Worker) | +| 13 | Permission Builder per Role | Simple toggle grid: rows = permissions, columns = roles. Flip switches on/off | +| 22 | Inline Tag/Nudge on Declaration Row | Small icon button on table row — click, pick employee, done. No modal needed | + +### Theme 4: UX & Workflow Efficiency + +| # | Idea | Description | +|---|------|-------------| +| 17 | Practicality-First Philosophy | Every UI decision passes: "Does this make the task faster or slower?" | +| 18 | Tables Over Cards for Data | Dense, scannable, sortable tables — accountants think in rows and columns | +| 20 | Bulk Declaration Creation | Select multiple clients → choose type (TVA Mensuel) → set date → create all at once | +| 21 | Bulk Notification Scheduling | Set notification date during bulk creation — all clients notified on same day | +| 23 | Advanced Filtering System | Consistent filter bar across Clients, Declarations, Archive — persistent until cleared | +| 24 | Archive-Specific Filters | Extra dimensions: fiscal year, declaration type, client, date range, document type | +| 25 | Quick Search | Search bar with instant text lookup — no page reload | +| 29 | Client-Initiated Declarations | Clients request declarations through portal — reverses the flow (future enhancement) | + +### Theme 5: Archive System + +| # | Idea | Description | +|---|------|-------------| +| 26 | Auto-Archive on Close | Declaration moves to archive automatically when status becomes Closed | +| 27 | Archive ≠ Delete | Full history preserved — documents, messages, actions. Filing cabinet, not trash | +| 30 | Hybrid Filters + Search | Flat list with fiscal year/client/type/date filters + search bar | +| 31 | Read-Only + Re-Open | Archive locked, but Owner/Manager can re-open → moves back to active Declarations | +| 32 | Re-Open Audit Trail | Who, when, why logged — legal protection for the firm | +| 33 | 10-Year Retention Policy | Automated lifecycle with warning notification before cleanup | +| 34 | Bulk Download as ZIP | Filter a set → download structured ZIP with all documents organized by declaration | +| 35 | In-App Document Preview | Preview button (PDF/image viewer) + separate download button | +| 36 | Metadata Search First | Search by client, type, year, date. Full-text document search in future phase | +| 37 | Archive as Main Nav Item | Top-level sidebar — first-class feature, not hidden in settings | +| 38 | Archive Detail Page | Read-only full snapshot: declaration info, timeline, documents, messages, portal actions | +| 39 | Visual Distinction | Muted color scheme / archive badge — clear boundary between active and archived | + +### Future Enhancements + +| # | Idea | Description | +|---|------|-------------| +| 5 | Archive as First-Class Feature | Accountants' institutional memory — organized by fiscal years and dossiers | +| 9 | Kanban View | Optional drag-and-drop board for declarations by status — visual pipeline management | + +--- + +## Implementation Roadmap + +### Phase 1: Role System Foundation +*Everything else depends on this* + +- #14 Fixed Roles (Owner, Manager, Worker) + Permission Matrix +- #15 Sensible Defaults +- #16 Per-Workspace Permission Storage +- #12 Team Management Page +- #13 Permission toggles per role +- #28 Move user management inside workspace + +### Phase 2: Dashboard Separation +*Now that roles exist, build the right view for each* + +- #11 Single Shell, Role-Driven Sidebar +- #3 Firm Admin Command Center +- #7 Scoped Employee Dashboard +- #1 SaaS Owner Platform Dashboard +- #4 Priority Alert System +- #19 Dashboard + Alerts combined +- #2 Issue/Support Inbox + +### Phase 3: Collaboration Features +*The nudge/notification layer* + +- #6 Quick Tag & Nudge +- #22 Inline Nudge on Declaration Row +- #8 Employee Notification Center + +### Phase 4: Workflow Efficiency +*The عملي features that save time* + +- #20 Bulk Declaration Creation +- #21 Bulk Notification Scheduling +- #23 Advanced Filtering across all views +- #25 Quick Search +- #17 Practicality-first UI pass +- #18 Tables for data views + +### Phase 5: Archive System +*Needs closed declarations to exist — builds on everything above* + +- #26 Auto-Archive on Close +- #27 Archive ≠ Delete (preservation) +- #37 Archive as Main Nav Item +- #30 Hybrid Filters + Search Bar +- #38 Archive Detail Page (read-only) +- #35 In-App Preview + Download buttons +- #31 Read-Only + Re-Open capability +- #32 Re-Open Audit Trail +- #34 Bulk Download as ZIP +- #39 Visual Distinction for archived items +- #33 10-Year Retention Policy +- #36 Metadata Search (full-text later) + +### Phase 6: Future Enhancements + +- #9 Kanban View +- #29 Client-Initiated Declarations +- #24 Archive-specific advanced filters + +--- + +## Session Summary and Insights + +**Key Achievements:** + +- 39 breakthrough ideas generated across 5 themes +- Clear 4-layer user model defined (SaaS Owner → Firm Admin → Worker → External Client) +- Core architectural decision made: Fixed Roles + Permission Overrides per workspace +- Complete archive system designed across 6 dimensions +- Practical implementation roadmap with dependency-ordered phases + +**Key Design Decisions Made:** + +1. **Single app, role-driven views** — no separate admin application +2. **Fixed roles (Owner/Manager/Worker)** with toggleable permission overrides per workspace +3. **Folders renamed to Declarations** — domain-specific language +4. **عملي-first UX** — practicality over beauty, tables over cards +5. **Archive = auto on close** — read-only with re-open capability +6. **10-year retention** — matching Moroccan legal requirements +7. **Metadata search first** — full-text document search deferred to future phase + +**Creative Facilitation Narrative:** + +This session moved efficiently from understanding (who are the users?) to structure (what do they need?) to refinement (how do we improve?) to design (build the archive). Saad's practical mindset — عملي first — anchored every decision in real-world accountant workflows. The bulk declaration creation idea emerged as a standout competitive feature, born from understanding that accountants work in batches by deadline, not individually by client. The archive system was designed with input from a real accountant, ensuring it serves actual needs rather than theoretical ones. diff --git a/_bmad-output/implementation-artifacts/0-1-rename-folders-to-declarations-in-database.md b/_bmad-output/implementation-artifacts/0-1-rename-folders-to-declarations-in-database.md new file mode 100644 index 0000000..9a70e72 --- /dev/null +++ b/_bmad-output/implementation-artifacts/0-1-rename-folders-to-declarations-in-database.md @@ -0,0 +1,284 @@ +# Story 0.1: Rename Folders to Declarations in Database + +Status: done + + + +## Story + +As a developer, +I want the database tables and columns to use "declaration" terminology instead of "folder", +so that the data layer reflects the correct professional terminology before building new features. + +## Acceptance Criteria + +1. **Given** the existing `folders` table exists with data, **When** the migration runs, **Then** the `folders` table is renamed to `declarations` +2. **And** all `folder_id` foreign key columns across related tables are renamed to `declaration_id` +3. **And** the `folder_invitations` table is renamed to `declaration_invitations` +4. **And** all indexes referencing "folder" are renamed to reference "declaration" +5. **And** the migration is reversible (rollback renames back to "folder") +6. **And** existing data is preserved with zero data loss + +## Tasks / Subtasks + +- [x] Task 1: Create the rename migration file (AC: #1, #2, #3, #4, #5, #6) + - [x] 1.1: Create migration `xxxx_rename_folders_to_declarations.php` + - [x] 1.2: In `up()` — Drop foreign key constraints: `messages_folder_id_foreign` and `folder_invitations_folder_id_foreign` + - [x] 1.3: In `up()` — Rename column `folder_id` → `declaration_id` in `messages` table + - [x] 1.4: In `up()` — Drop index `messages_folder_id_created_at_index`, recreate as `[declaration_id, created_at]` + - [x] 1.5: In `up()` — Rename column `folder_id` → `declaration_id` in `folder_invitations` table + - [x] 1.6: In `up()` — Rename table `folders` → `declarations` + - [x] 1.7: In `up()` — Rename table `folder_invitations` → `declaration_invitations` + - [x] 1.8: In `up()` — Re-add FK constraints using explicit `->constrained('declarations')->cascadeOnDelete()` on both tables + - [x] 1.9: Implement `down()` with explicit reverse steps (see Dev Notes) +- [x] Task 2: Verify migration executes successfully (AC: #1, #2, #3, #4, #6) + - [x] 2.1: Run `php artisan migrate` — confirm zero errors + - [x] 2.2: Verify tables: `php artisan tinker --execute="echo Schema::hasTable('declarations') ? 'OK' : 'FAIL';"` + - [x] 2.3: Verify columns: `php artisan tinker --execute="echo Schema::hasColumn('messages', 'declaration_id') ? 'OK' : 'FAIL';"` + - [x] 2.4: Verify composite index exists on `messages(declaration_id, created_at)` + - [x] 2.5: Verify existing seed/test data is intact after migration +- [x] Task 3: Verify rollback works (AC: #5) + - [x] 3.1: Run `php artisan migrate:rollback` — confirm tables revert to `folders`, `folder_invitations` + - [x] 3.2: Verify foreign key columns revert to `folder_id` + - [x] 3.3: Re-run `php artisan migrate` to confirm re-apply also works + +## Dev Notes + +### Critical Architecture Constraints + +- **Database engine:** MySQL 8.4 (production) / SQLite for local dev (check `.env`) +- **Multi-tenant:** All tables are workspace-scoped. The migration is schema-only (rename), no data transformation needed +- **Spatie media_library:** The `media` table has a polymorphic `model_type` column storing `App\Models\Folder` (FQCN, no morph map registered). NOT addressed in this story — handled in Story 0.2 when the model class is renamed +- **Spatie activity_log:** The `activity_log` table has `subject_type` storing `App\Models\Folder`. Same as above — handled in Story 0.2 +- **No morph map:** The codebase has no `Relation::morphMap()` registration — polymorphic types store FQCN directly. Data updates for these columns are a Story 0.2 concern +- **Notifications table:** Has `notifiable_type` morphs — not folder-related, no action needed + +### Migration Strategy — Order of Operations + +The order matters to avoid foreign key constraint violations: + +**`up()` method:** +1. Drop FK constraints: `$table->dropForeign('messages_folder_id_foreign')` and `$table->dropForeign('folder_invitations_folder_id_foreign')` +2. Rename columns `folder_id` → `declaration_id` via `$table->renameColumn('folder_id', 'declaration_id')` in `messages` and `folder_invitations` +3. Drop index `messages_folder_id_created_at_index`, create new `$table->index(['declaration_id', 'created_at'])` +4. `Schema::rename('folders', 'declarations')` +5. `Schema::rename('folder_invitations', 'declaration_invitations')` +6. Re-add FKs: `$table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete()` on both `messages` and `declaration_invitations` + +**`down()` method (explicit reverse):** +1. Drop FK constraints: `$table->dropForeign('messages_declaration_id_foreign')` and `$table->dropForeign('declaration_invitations_declaration_id_foreign')` +2. `Schema::rename('declaration_invitations', 'folder_invitations')` +3. `Schema::rename('declarations', 'folders')` +4. Drop index on `messages(declaration_id, created_at)`, recreate as `$table->index(['folder_id', 'created_at'])` +5. Rename columns `declaration_id` → `folder_id` in `messages` and `folder_invitations` +6. Re-add FKs: `$table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete()` on both tables + +**Critical FK rule:** Always use explicit `->on('declarations')` (or `->on('folders')` in down) — never bare `->constrained()` which infers table from column name and can cause errors during rename transitions. + +### Tables and Columns Affected + +| Table | Column/Index/Constraint | Action | +|---|---|---| +| `folders` | (entire table) | Rename → `declarations` | +| `folder_invitations` | (entire table) | Rename → `declaration_invitations` | +| `folder_invitations` | `folder_id` column | Rename → `declaration_id` | +| `folder_invitations` | FK `folder_invitations_folder_id_foreign` | Drop, re-add as `declaration_invitations_declaration_id_foreign` | +| `messages` | `folder_id` column | Rename → `declaration_id` | +| `messages` | FK `messages_folder_id_foreign` | Drop, re-add as `messages_declaration_id_foreign` | +| `messages` | Index `messages_folder_id_created_at_index` | Drop, recreate with `declaration_id` | + +### SQLite vs MySQL Considerations + +- **Laravel 12 handles `renameColumn()` natively** without doctrine/dbal. No additional package installation is needed +- **SQLite** (local dev): `Schema::rename()` and `renameColumn()` work natively. Laravel handles SQLite's limited `ALTER TABLE` via internal table recreation +- **MySQL 8.4** (production): Native `RENAME TABLE` and `ALTER TABLE ... RENAME COLUMN` are well-supported +- **Recommendation:** Use Laravel's `Schema` facade methods exclusively — never raw SQL — for cross-driver compatibility + +### Warning: Application Will Break After This Migration + +After running this migration, the application will be non-functional until Story 0.2 (backend rename) is completed. **Stories 0.1 and 0.2 should be implemented in the same development session.** + +**Why it breaks:** The `Folder` model has no explicit `$table` property — it relies on Laravel convention to infer `folders`. After rename, Laravel looks for `folders` which no longer exists. + +**Specific breakage points (all fixed in Story 0.2):** +- `Folder` model → table `folders` not found +- `FolderInvitation` model → table `folder_invitations` not found, `$fillable` contains `folder_id` (column is now `declaration_id`) +- `Message` model → `$fillable` contains `folder_id`, `folder()` relationship references wrong column +- Middleware alias `folder.invitation` in `bootstrap/app.php` and route reference in `web.php` → Story 0.2 scope + +### Existing Migration Files for Reference + +These are the original migration files that created the tables being renamed: + +- `database/migrations/2026_02_27_103316_create_folders_table.php` — Creates `folders` table with all columns +- `database/migrations/2026_03_01_144731_create_folder_invitations_table.php` — Creates `folder_invitations` with `folder_id` FK +- `database/migrations/2026_03_01_144730_create_messages_table.php` — Creates `messages` with `folder_id` FK and composite index +- `database/migrations/2026_03_01_144736_add_confirmation_fields_to_folders_table.php` — Adds confirmation columns to `folders` +- `database/migrations/2026_03_09_000003_split_vat_folder_type.php` — Data migration on `folders` table + +### Testing Standards + +- Use **Pest** syntax (`test()` closures), never PHPUnit class-based tests +- `RefreshDatabase` is auto-applied via `Pest.php` — don't add it manually +- Run tests: `composer test` +- After migration, existing tests WILL fail (they reference `Folder` model). This is expected — Story 0.2 fixes them + +### Project Structure Notes + +- Migration file goes in: `database/migrations/` +- Naming convention: `YYYY_MM_DD_HHMMSS_rename_folders_to_declarations.php` +- **Migration timestamp MUST be after `2026_03_09_000005`** (latest existing migration). Use e.g. `2026_03_11_000001_rename_folders_to_declarations.php` +- This ensures the rename runs after `split_vat_folder_type.php` (which references `DB::table('folders')`) on both `migrate` and `migrate:fresh` +- Laravel 12 handles column renames natively — no doctrine/dbal needed + +### Migration Code Skeleton + +```php +dropForeign('messages_folder_id_foreign'); + }); + Schema::table('folder_invitations', function (Blueprint $table) { + $table->dropForeign('folder_invitations_folder_id_foreign'); + }); + + // 2. Rename columns + Schema::table('messages', function (Blueprint $table) { + $table->renameColumn('folder_id', 'declaration_id'); + }); + Schema::table('folder_invitations', function (Blueprint $table) { + $table->renameColumn('folder_id', 'declaration_id'); + }); + + // 3. Drop old index, create new + Schema::table('messages', function (Blueprint $table) { + $table->dropIndex('messages_folder_id_created_at_index'); + $table->index(['declaration_id', 'created_at']); + }); + + // 4-5. Rename tables + Schema::rename('folders', 'declarations'); + Schema::rename('folder_invitations', 'declaration_invitations'); + + // 6. Re-add FK constraints + Schema::table('messages', function (Blueprint $table) { + $table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete(); + }); + Schema::table('declaration_invitations', function (Blueprint $table) { + $table->foreign('declaration_id')->references('id')->on('declarations')->cascadeOnDelete(); + }); + } + + public function down(): void + { + // 1. Drop FK constraints + Schema::table('messages', function (Blueprint $table) { + $table->dropForeign('messages_declaration_id_foreign'); + }); + Schema::table('declaration_invitations', function (Blueprint $table) { + $table->dropForeign('declaration_invitations_declaration_id_foreign'); + }); + + // 2-3. Rename tables back + Schema::rename('declaration_invitations', 'folder_invitations'); + Schema::rename('declarations', 'folders'); + + // 4. Drop new index, recreate old + Schema::table('messages', function (Blueprint $table) { + $table->dropIndex('messages_declaration_id_created_at_index'); + $table->index(['folder_id', 'created_at']); + }); + + // 5. Rename columns back + Schema::table('messages', function (Blueprint $table) { + $table->renameColumn('declaration_id', 'folder_id'); + }); + Schema::table('folder_invitations', function (Blueprint $table) { + $table->renameColumn('declaration_id', 'folder_id'); + }); + + // 6. Re-add FK constraints + Schema::table('messages', function (Blueprint $table) { + $table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete(); + }); + Schema::table('folder_invitations', function (Blueprint $table) { + $table->foreign('folder_id')->references('id')->on('folders')->cascadeOnDelete(); + }); + } +}; +``` + +### References + +- [Source: _bmad-output/planning-artifacts/epics.md#Story 0.1] +- [Source: _bmad-output/planning-artifacts/architecture.md#Pre-Phase Migration] +- [Source: _bmad-output/planning-artifacts/architecture.md#Declaration Status Flow (lines 453-479)] +- [Source: _bmad-output/project-context.md#Technology Stack] +- [Source: database/migrations/2026_02_27_103316_create_folders_table.php] +- [Source: database/migrations/2026_03_01_144731_create_folder_invitations_table.php] +- [Source: database/migrations/2026_03_01_144730_create_messages_table.php] + +## Dev Agent Record + +### Agent Model Used + +Claude Opus 4.6 + +### Debug Log References + +- SQLite does not support `dropForeign('constraint_name')` — switched to column array syntax `dropForeign(['column_name'])` for cross-driver compatibility +- `down()` method required reordering: must drop index before renaming columns, then recreate index after rename (SQLite validates indexes during column rename) +- Upgraded Docker Sail container from PHP 8.2 to PHP 8.4 to resolve PHPUnit 12.5 / sebastian/environment 8.0 incompatibility (typed class constants require PHP 8.3+) +- Fixed `phpunit.xml` to use `DB_CONNECTION=sqlite` and `DB_DATABASE=:memory:` for test isolation + +### Completion Notes List + +- Created migration `2026_03_11_000001_rename_folders_to_declarations.php` implementing full rename of tables, columns, indexes, and FK constraints +- Migration uses column array syntax for `dropForeign()` ensuring SQLite (dev) and MySQL (prod) compatibility +- `up()` follows exact order: drop FKs → drop index → rename columns → recreate index → rename tables → re-add FKs +- `down()` fully reverses all changes: drop FKs → rename tables → drop index → rename columns → recreate index → re-add FKs +- Added 6 Pest tests verifying table renames, column renames, composite index, and rollback in `tests/Feature/Database/RenameFoldersToDeclarationsTest.php` +- All 6 migration tests pass; all non-Folder-related tests pass (40+ tests) +- Folder-related tests (FolderTypeTest, MediaDownloadTest, FolderMentionTest, NotificationControllerTest) fail as expected — documented in Dev Notes, fixed in Story 0.2 +- Infrastructure fixes: PHP 8.2→8.4 in compose.yaml, WWWGROUP/WWWUSER in .env, phpunit.xml DB config + +### Senior Developer Review (AI) + +**Reviewer:** Saad (via Claude Opus 4.6) on 2026-03-11 + +**Findings (8 total):** 3 High, 3 Medium, 2 Low + +| # | Severity | Issue | Resolution | +|---|----------|-------|------------| +| H1 | HIGH | `up()` dropIndex after column rename — fragile on SQLite | Fixed: moved dropIndex before renameColumn | +| H2 | HIGH | Missing tests for AC #4 (indexes), AC #5 (reversibility) | Fixed: added 2 new Pest tests | +| H3 | HIGH | Task 2.4 marked complete but no index test | Fixed: composite index test added | +| M1 | MEDIUM | Infrastructure changes (compose.yaml, phpunit.xml) bundled in rename story | Noted: these are prerequisites for running tests — documented as infra scope in File List | +| M2 | MEDIUM | `.env` modified but not in File List | Fixed: added to File List | +| M3 | MEDIUM | Inconsistent FK drop/create approach between up() and down() | Re-evaluated: both up() and down() use identical pattern (column array drops, explicit references for creates) — actually consistent, no fix needed | +| L1 | LOW | split_vat_folder_type.php has implicit dependency on migration order | Fixed: added dependency comment to rename migration file | +| L2 | LOW | Test directory `tests/Feature/Migration/` doesn't mirror controller structure | Fixed: renamed to `tests/Feature/Database/` | + +### Change Log + +- 2026-03-11: Code review follow-up — added migration dependency comment (L1), renamed test dir Migration/ → Database/ (L2), re-evaluated M3 as non-issue. All actionable findings resolved. +- 2026-03-11: Code review — fixed migration step ordering (H1), added index and rollback tests (H2/H3), updated File List (M2). 3 HIGH and 1 MEDIUM fixed, 2 MEDIUM and 2 LOW noted. +- 2026-03-11: Implemented Story 0.1 — database rename from folders to declarations terminology. Migration created, verified forward/backward, tests added. Infrastructure upgraded to PHP 8.4. + +### File List + +- database/migrations/2026_03_11_000001_rename_folders_to_declarations.php (new) +- tests/Feature/Database/RenameFoldersToDeclarationsTest.php (new) +- compose.yaml (modified — PHP 8.2 → 8.4) +- phpunit.xml (modified — DB_CONNECTION=sqlite, DB_DATABASE=:memory:) +- .env (modified — added WWWGROUP=1000, WWWUSER=1000) diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml new file mode 100644 index 0000000..5f652bb --- /dev/null +++ b/_bmad-output/implementation-artifacts/sprint-status.yaml @@ -0,0 +1,113 @@ +# generated: 2026-03-11 +# project: l'ami fiduciaire +# project_key: NOKEY +# tracking_system: file-system +# story_location: _bmad-output/implementation-artifacts + +# STATUS DEFINITIONS: +# ================== +# Epic Status: +# - backlog: Epic not yet started +# - in-progress: Epic actively being worked on +# - done: All stories in epic completed +# +# Epic Status Transitions: +# - backlog → in-progress: Automatically when first story is created (via create-story) +# - in-progress → done: Manually when all stories reach 'done' status +# +# Story Status: +# - backlog: Story only exists in epic file +# - ready-for-dev: Story file created in stories folder +# - in-progress: Developer actively working on implementation +# - review: Ready for code review (via Dev's code-review workflow) +# - done: Story completed +# +# Retrospective Status: +# - optional: Can be completed but not required +# - done: Retrospective has been completed +# +# WORKFLOW NOTES: +# =============== +# - Epic transitions to 'in-progress' automatically when first story is created +# - Stories can be worked in parallel if team capacity allows +# - SM typically creates next story after previous one is 'done' to incorporate learnings +# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended) + +generated: 2026-03-11 +project: "l'ami fiduciaire" +project_key: NOKEY +tracking_system: file-system +story_location: "_bmad-output/implementation-artifacts" + +development_status: + # Epic 0: Foundation Migration & Infrastructure Setup + epic-0: in-progress + 0-1-rename-folders-to-declarations-in-database: done + 0-2-rename-folders-to-declarations-in-backend: backlog + 0-3-rename-folders-to-declarations-in-frontend: backlog + 0-4-configure-redis-for-cache-queue-and-sessions: backlog + 0-5-add-foundation-database-migrations-and-declaration-status-flow: backlog + epic-0-retrospective: optional + + # Epic 1: Team Management & Permission System + epic-1: backlog + 1-1-permission-configuration-and-controller-traits: backlog + 1-2-team-management-page-view-and-invite-members: backlog + 1-3-role-assignment-and-member-removal: backlog + 1-4-manager-permission-toggle-matrix: backlog + 1-5-role-based-access-enforcement-across-views: backlog + 1-6-workspace-switching-for-multi-workspace-owners: backlog + epic-1-retrospective: optional + + # Epic 2: Role-Driven Dashboard & Command Center + epic-2: backlog + 2-1-owner-manager-command-center-dashboard: backlog + 2-2-priority-alerts-panel: backlog + 2-3-worker-scoped-dashboard: backlog + 2-4-dashboard-activity-feed: backlog + epic-2-retrospective: optional + + # Epic 3: Collaboration, Nudge System & Notifications + epic-3: backlog + 3-1-notification-infrastructure-setup: backlog + 3-2-one-click-nudge-system: backlog + 3-3-notification-center-and-bell: backlog + 3-4-bulk-client-notification-scheduling: backlog + 3-5-email-notification-enhancement-for-key-events: backlog + epic-3-retrospective: optional + + # Epic 4: Bulk Operations, Search & Advanced Filtering + epic-4: backlog + 4-1-filterbar-component-and-usefilters-composable: backlog + 4-2-apply-filterbar-to-declarations-and-clients-pages: backlog + 4-3-quick-search-with-mysql-fulltext: backlog + 4-4-bulk-declaration-creation: backlog + 4-5-archive-navigation-with-dedicated-filters: backlog + epic-4-retrospective: optional + + # Epic 5: Archive System & Document Preview + epic-5: backlog + 5-1-auto-archive-on-close-and-history-preservation: backlog + 5-2-archive-detail-page-read-only-snapshot: backlog + 5-3-in-app-document-preview: backlog + 5-4-re-open-archived-declaration: backlog + 5-5-bulk-zip-download-and-retention-policy: backlog + epic-5-retrospective: optional + + # Epic 6: Platform Administration & Subscription Enforcement + epic-6: backlog + 6-1-saas-admin-role-and-admin-guard: backlog + 6-2-platform-dashboard-and-workspace-overview: backlog + 6-3-support-ticket-inbox: backlog + 6-4-platform-configuration-management: backlog + 6-5-subscription-tier-limit-enforcement: backlog + epic-6-retrospective: optional + + # Epic 7: Production Infrastructure & Deployment + epic-7: backlog + 7-1-encrypted-file-storage-with-s3-compatible-backend: backlog + 7-2-email-delivery-via-amazon-ses: backlog + 7-3-laravel-forge-deployment-and-ci-pipeline: backlog + 7-4-monitoring-alerting-and-error-tracking: backlog + 7-5-automated-backups-and-disaster-recovery: backlog + epic-7-retrospective: optional diff --git a/_bmad-output/planning-artifacts/architecture.md b/_bmad-output/planning-artifacts/architecture.md new file mode 100644 index 0000000..4c100c5 --- /dev/null +++ b/_bmad-output/planning-artifacts/architecture.md @@ -0,0 +1,1264 @@ +--- +stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8] +lastStep: 8 +status: 'complete' +completedAt: '2026-03-11' +inputDocuments: + - '_bmad-output/planning-artifacts/product-brief-l-ami-fiduciaire-2026-03-10.md' + - '_bmad-output/planning-artifacts/prd.md' + - '_bmad-output/planning-artifacts/prd-validation-report.md' + - '_bmad-output/planning-artifacts/ux-design-specification.md' + - '_bmad-output/planning-artifacts/research/market-fiduciary-saas-morocco-research-2026-03-10.md' + - '_bmad-output/planning-artifacts/research/domain-moroccan-fiduciary-operations-research-2026-03-10.md' + - '_bmad-output/planning-artifacts/research/ecosystem-partners-morocco-fiduciary-research-2026-03-10.md' + - '_bmad-output/planning-artifacts/research/cloud-adoption-saas-trends-future-outlook-research-2026-03-11.md' + - '_bmad-output/planning-artifacts/research/domain-moroccan-tax-regulation-digital-compliance-research-2026-03-10.md' + - '_bmad-output/project-context.md' + - 'docs/index.md' + - 'docs/project-overview.md' + - 'docs/architecture.md' + - 'docs/development-guide.md' + - 'docs/source-tree-analysis.md' +workflowType: 'architecture' +project_name: "l'ami fiduciaire" +user_name: 'Saad' +date: '2026-03-11' +--- + +# Architecture Decision Document + +_This document builds collaboratively through step-by-step discovery. Sections are appended as we work through each architectural decision together._ + +## Project Context Analysis + +### Requirements Overview + +**Functional Requirements (58 FRs across 10 groups):** + +The 58 FRs decompose into three architectural tiers: + +| Tier | FR Groups | Architectural Impact | +|---|---|---| +| **Foundation** | Workspace & Onboarding (FR1-FR6), Team & Role Management (FR7-FR11), Client Management (FR12-FR15) | Multi-tenant data model, RBAC engine, session-based workspace resolution | +| **Core Domain** | Declaration Lifecycle (FR16-FR23), Dashboard & Visibility (FR24-FR28), Collaboration & Notifications (FR29-FR33), Client Portal & Document Exchange (FR34-FR40) | Declaration state machine, role-scoped queries, notification service, token-based portal | +| **Power Features** | Search/Filtering/Navigation (FR41-FR44), Archive System (FR45-FR51), Activity & Audit (FR52-FR55), Platform Administration (FR56-FR58) | Full-text search, archive storage strategy, audit trail, admin dashboard | + +The Foundation tier is partially built (multi-tenant workspaces, client CRUD, basic folder system). Core Domain and Power Features are the MVP build scope. + +**Non-Functional Requirements (28 NFRs across 4 categories):** + +| Category | Key NFRs | Architectural Driver | +|---|---|---| +| **Performance** (NFR1-6) | 2s page loads, 10s bulk operations (50 items), 1s search, 3s dashboard render for 200 clients | Server-side rendering optimization, query performance, pagination strategy | +| **Security** (NFR7-15) | TLS 1.2+, AES-256 at rest, tenant isolation at every query, token security, 2FA, CNDP compliance, EU hosting | Encryption layer, middleware architecture, hosting infrastructure | +| **Scalability** (NFR16-20) | 200 concurrent workspaces, 1,000 users, 500 clients/workspace, 5,000 declarations/workspace, 1TB storage, 2-3x peak handling | Database indexing, query optimization, storage architecture, caching | +| **Reliability** (NFR21-28) | 99.5% uptime, 1hr RPO, 1hr RTO, >99% email delivery, zero data loss tolerance | Backup strategy, email service selection, monitoring, queue reliability | + +**UX-Driven Architectural Requirements:** + +- Role-driven sidebar and dashboard content (4 internal roles + client portal = 5 distinct view contexts) +- Inline table actions without page navigation (requires component-level interaction patterns) +- Filter persistence across views (session/URL state management) +- Bulk operations with real-time feedback (progress indication for 50+ item operations) +- Summary cards that filter tables below (client-side or server-side filter linkage) +- Server-side pagination as Inertia.js constraint (no infinite scroll) +- Mobile-first client portal alongside desktop-first internal app + +### Scale & Complexity + +- **Primary domain:** Full-stack web (Laravel 12 + Vue 3 monolith via Inertia.js 2) +- **Complexity level:** High +- **Estimated architectural components:** ~15-20 major components (auth, RBAC, tenant isolation, declaration engine, notification service, document storage, client portal, dashboard aggregation, archive system, search/filter, audit logging, admin panel, email delivery, file preview, bulk operations) + +### Technical Constraints & Dependencies + +**Brownfield Constraints (non-negotiable):** + +| Constraint | Impact | +|---|---| +| Laravel 12 + PHP 8.2+ | Backend framework, ORM, routing, middleware patterns locked in | +| Vue 3 + TypeScript strict | Frontend framework, component patterns, type system | +| Inertia.js 2.0 | No REST API layer -- controllers render pages directly. Server-driven SPA. All URLs from PHP controllers. | +| shadcn-vue (reka-ui) + Tailwind CSS 4 | UI component library, styling approach. Cannot modify `components/ui/*` | +| MySQL 8.4 | Database engine. Session-based tenant resolution via `current_workspace_id` | +| Spatie Media Library | File upload/download infrastructure | +| Spatie Activity Log | Audit trail infrastructure | +| Laravel Fortify | Authentication + 2FA | +| bensampo/laravel-enum | Enum pattern for business domain values | +| Laravel Wayfinder | Type-safe frontend route generation | +| Pest 4 | Testing framework (PHP-only, no frontend JS tests) | +| Docker/Sail + GitHub Actions | Dev environment + CI/CD | + +**Regulatory Constraints:** + +| Regulation | Requirement | +|---|---| +| CNDP (Law 09-08) | EU-hosted infrastructure, data retention policies, user consent mechanisms | +| AML (Law 43-05) | Audit trails supporting firms' compliance obligations | +| Tax compliance context | Declaration types aligned with Moroccan fiscal calendar | +| 10-year retention | Archived data must be preserved for legal minimum | + +**Infrastructure Constraints:** + +| Constraint | Implication | +|---|---| +| Moroccan internet (ADSL/4G) | Page loads must work on slower connections -- server-side rendering, optimized asset delivery | +| Fiscal deadline peaks (15th-20th monthly, Jan-Mar annually) | System must handle 2-3x normal load without degradation | +| Email delivery during peaks | Mission-critical -- failed email = missed filing = financial penalty for client | + +### Cross-Cutting Concerns Identified + +1. **Tenant Isolation** -- Every database query, every file access, every API response must be scoped to the current workspace. This is not just a middleware concern -- it's a per-model, per-controller, per-query concern. Existing pattern: session-based `current_workspace_id` with `abort(404)` for violations. + +2. **Role-Based Authorization** -- Three-tier access (Owner sees all, Manager sees all with configurable actions, Worker sees only assigned items). Affects every controller method, every Eloquent query scope, every Vue component's visible content. Current pattern: custom `authorizeXxx()` methods (no Gates/Policies). + +3. **Audit Trail** -- Every data modification logged with actor, timestamp, and change details (FR52-NFR12). Already implemented via Spatie Activity Log trait. Must extend to all new models. + +4. **Email Notification Reliability** -- 5 existing email types, more to come (nudge notifications, bulk scheduling). >99% delivery rate required. Queue-based sending with retry logic needed. + +5. **File Security** -- Client financial documents (CIN numbers, bank statements, tax documents) require encryption at rest. Access limited to authorized workspace members and specific client via token link. + +6. **Search & Filtering** -- Consistent filter bar across Clients, Declarations, Archive views with session persistence. Quick search across views. Affects frontend component architecture and backend query building. + +7. **Bulk Operations** -- Creating/notifying/updating 50+ items at once. Must be performant (10s target), provide feedback, and handle partial failures gracefully. Queue-based processing likely needed. + +## Starter Template Evaluation + +### Primary Technology Domain + +Full-stack web monolith (Laravel 12 + Vue 3 via Inertia.js 2.0) -- **brownfield project with established stack**. + +### Starter Assessment: Brownfield Context + +This project does not require a new starter template. The foundation is an active codebase with: + +- **Backend:** Laravel 12, PHP 8.2+ (8.4 runtime), Eloquent ORM, 7 models, 16 migrations +- **Frontend:** Vue 3.5, TypeScript 5.2 strict, 31 page components, shadcn-vue design system (20+ component groups) +- **Bridge:** Inertia.js 2.0 (server-driven SPA -- no REST API layer) +- **Styling:** Tailwind CSS 4.1 with CVA, `cn()` helper +- **Auth:** Laravel Fortify (login, registration, password reset, email verification, 2FA/TOTP) +- **Files:** Spatie Media Library 11 (upload/download) +- **Logging:** Spatie Activity Log 4 (audit trail) +- **Enums:** bensampo/laravel-enum 6 (9 business enums) +- **Routes:** Laravel Wayfinder 0.1.9 (type-safe frontend routes) +- **Icons:** Lucide (lucide-vue-next) +- **Testing:** Pest 4 (PHP-only), 13 feature test files +- **Code Quality:** Laravel Pint, ESLint 9, Prettier 3 (with Tailwind plugin) +- **Build:** Vite 7, SSR enabled +- **Dev Environment:** Docker/Sail (MySQL 8.4, Mailpit, Soketi) +- **CI/CD:** GitHub Actions (lint + test workflows) + +### Architectural Decisions Already Established + +**Language & Runtime:** +- PHP 8.2+ with strict typing conventions, explicit `$fillable`, Form Request classes, PHPDoc generics on relationships +- TypeScript 5.2 strict mode, `isolatedModules: true`, `import type` for type-only imports, `@/` path alias + +**Styling Solution:** +- Tailwind CSS 4.1 via `@tailwindcss/vite` plugin, shadcn-vue components (headless reka-ui primitives), CVA for variants, `cn()` utility + +**Build Tooling:** +- Vite 7 with `laravel-vite-plugin` 2.0, SSR support enabled, concurrent dev server (PHP + Queue + Logs + Vite) + +**Testing Framework:** +- Pest 4 with `RefreshDatabase` auto-applied, `test()` closures, `expect()` chaining, `route()` helper in tests + +**Code Organization:** +- Pages: `resources/js/pages/{domain}/{Action}.vue` +- Components: `resources/js/components/` (app) + `components/ui/` (shadcn-vue, never modify) +- Types: `resources/js/types/` with barrel `index.ts` +- Composables: `resources/js/composables/` +- Controllers: `app/Http/Controllers/` +- Requests: `app/Http/Requests/` +- Models: `app/Models/` +- Enums: `app/Enums/` +- Mail: `app/Mail/` + +**Development Experience:** +- `composer dev` runs all services concurrently (server + queue + logs + Vite HMR) +- `composer test` runs Pint lint check then Pest tests +- `npm run lint` + `npm run format` for JS/Vue code quality +- Docker/Sail for consistent local environment + +### Stack Gaps for MVP (Requiring Architectural Decisions) + +| Gap | Options to Evaluate | Decision Step | +|---|---|---| +| Permission toggle system | JSON column on pivot vs. permissions table | Step 4 | +| Search implementation | MySQL FULLTEXT vs. Laravel Scout + Meilisearch | Step 4 | +| In-app notifications | `DatabaseNotification` vs. custom notifications table | Step 4 | +| Bulk operation processing | Synchronous vs. queued jobs with progress broadcasting | Step 4 | +| Archive strategy | Soft state on declarations vs. separate archive table | Step 4 | +| Filter persistence | URL query params vs. session storage | Step 4 | +| Document preview | Client-side PDF.js vs. server-generated previews | Step 4 | +| Real-time features | Soketi (configured, not wired) -- scope of use for MVP | Step 4 | + +**Note:** No new starter initialization needed. First implementation story should be the "Folders to Declarations" terminology migration (Pre-Phase from PRD). + +## Core Architectural Decisions + +### Decision Priority Analysis + +**Critical Decisions (Block Implementation):** +- Permission toggle storage (JSON on pivot) -- blocks Phase 1 RBAC +- Archive strategy (status-based `archived_at`) -- blocks Phase 5 archive system +- Queue driver (Redis) -- blocks reliable email delivery and bulk operations +- Email service provider (Amazon SES EU) -- blocks notification reliability + +**Important Decisions (Shape Architecture):** +- Search implementation (MySQL FULLTEXT) -- shapes Phase 4 search/filter +- In-app notifications (Laravel DatabaseNotification) -- shapes Phase 3 nudge system +- Filter persistence (URL query params) -- shapes Phase 4 filtering UX +- Caching strategy (Redis) -- shapes dashboard performance +- File storage (S3-compatible) -- shapes document management scalability +- Hosting provider (EU VPS + Laravel Forge) -- shapes deployment pipeline + +**Deferred Decisions (Post-MVP):** +- Real-time WebSocket features (Soketi) -- not needed for MVP launch +- Full observability stack (Grafana/Prometheus) -- Laravel Pulse + Sentry sufficient for MVP +- Meilisearch integration -- MySQL FULLTEXT sufficient at initial scale +- Progress broadcasting for bulk operations -- synchronous sufficient for MVP batch sizes + +### Data Architecture + +**D1: Permission Toggle Storage** +- **Decision:** JSON column (`permissions`) on the `workspace_user` pivot table +- **Rationale:** 3 fixed roles with ~12 toggle-able permissions is a small, well-bounded set. JSON avoids extra JOINs on every permission check. Permissions are read frequently, written rarely (only when Owner configures Manager permissions). +- **Implementation:** Add `permissions` JSON column to `workspace_user` migration. Default permissions per role defined in a config file or enum. `WorkspaceUser` model casts `permissions` to array. +- **Affects:** FR3, FR4, FR7-FR11 (all role/permission features) + +**D2: Archive Strategy** +- **Decision:** Status-based archiving with `archived_at` timestamp column on declarations table +- **Rationale:** Declarations stay in the same table, filtered out of active views via `whereNull('archived_at')` scope. Re-open is trivial (`archived_at = null`). At 5,000 declarations per workspace, a single indexed table performs well. 10-year retention is simply not deleting rows. +- **Implementation:** Add `archived_at` nullable timestamp to declarations migration. Add `scopeActive()` and `scopeArchived()` Eloquent scopes. Auto-set `archived_at` when status becomes "Closed." +- **Affects:** FR21-FR23, FR45-FR51 (entire archive system) + +**D3: Search Implementation** +- **Decision:** MySQL FULLTEXT indexes for quick search across clients and declarations +- **Rationale:** No additional service dependency. At 200 workspaces with 500 clients / 5,000 declarations each, MySQL FULLTEXT delivers sub-second results. Workspace-scoped queries limit the search space further. Can migrate to Meilisearch post-MVP if search demands grow. +- **Implementation:** Add FULLTEXT indexes on searchable columns (client name, declaration type/notes, etc.). Use `MATCH...AGAINST` in natural language mode. Laravel Scout can be added later as an abstraction layer without changing the search API. +- **Affects:** FR43, NFR6 (quick search, 1-second response) + +**D4: Caching Strategy** +- **Decision:** Redis for application caching +- **Rationale:** Triple-duty Redis (cache + queue + sessions) is the standard Laravel production pattern. Dashboard aggregation queries (counts by status, overdue items) benefit from short-lived cache. Redis is already supported by Laravel Sail. +- **Implementation:** Add Redis service to Docker Compose. Set `CACHE_DRIVER=redis`, `QUEUE_CONNECTION=redis`, `SESSION_DRIVER=redis` in environment. Use `Cache::remember()` for dashboard aggregations with 5-minute TTL. Invalidate on status changes. +- **Affects:** NFR1, NFR5, NFR16-20 (performance, scalability) + +### Authentication & Security + +**D5: Queue Driver** +- **Decision:** Redis queue driver +- **Rationale:** Already adding Redis for cache/sessions. Redis queues are fast, support delayed jobs, retries (up to 3 within 5 minutes per NFR26), and job batching. Native Laravel support. +- **Implementation:** Set `QUEUE_CONNECTION=redis`. Email mailables dispatched via `dispatch()` or `Mail::queue()`. Failed jobs table for monitoring. `queue:work` process managed by Supervisor in production. +- **Affects:** NFR26 (email delivery reliability), FR32-FR33 (notification scheduling) + +**D6: File Encryption at Rest** +- **Decision:** Storage-level encryption (provider-managed AES-256 SSE) +- **Rationale:** Meets NFR7 (AES-256 at rest) transparently without breaking Spatie Media Library or adding application-level encryption complexity. Encryption/decryption is handled by the storage provider with zero performance overhead for the application. +- **Implementation:** Enable SSE (Server-Side Encryption) on the S3-compatible bucket. All objects encrypted automatically. No application code changes needed. +- **Affects:** NFR7 (encryption at rest), NFR14 (document access control) + +**D7: Session Storage** +- **Decision:** Redis sessions +- **Rationale:** Consistent with Redis for cache and queue (triple-duty). Fast session reads, supports horizontal scaling if needed later. Session-based workspace resolution (`current_workspace_id`) benefits from Redis speed. +- **Implementation:** Set `SESSION_DRIVER=redis`. No application code changes -- Laravel handles session management transparently. +- **Affects:** Multi-tenant workspace resolution (session-based `current_workspace_id`) + +### Communication Patterns + +**D8: In-App Notification System** +- **Decision:** Laravel's built-in `DatabaseNotification` system +- **Rationale:** Battle-tested, supports database + mail channels, read/unread tracking built-in, `Notifiable` trait already on User model via Fortify. Workspace scoping added via query constraints when fetching notifications. +- **Implementation:** Run `php artisan notifications:table` migration. Create notification classes (e.g., `NudgeNotification`, `DeclarationOverdueNotification`). Query notifications with workspace scope via the related declaration/client. Mark as read via `markAsRead()`. +- **Affects:** FR29-FR31 (nudge system, notification center) + +**D9: Bulk Operation Processing** +- **Decision:** Synchronous processing for bulk creation/updates; individual email sends queued via Redis +- **Rationale:** 50 DB inserts complete in <1 second synchronously. Email notifications for those 50 items are dispatched to Redis queue individually, processing asynchronously. This meets the 10-second NFR target without the complexity of progress broadcasting. Controller returns immediately after DB operations; emails process in background. +- **Implementation:** Bulk creation: `Declaration::insert($records)` or loop with `create()` for model events. Each notification dispatched to queue: `Notification::send($users, new BulkNotification())`. Failed individual sends retry automatically via Redis queue. +- **Affects:** FR17, FR32 (bulk declaration creation, bulk notification scheduling), NFR2-3 (10-second target) + +**D10: Real-Time Features (Soketi)** +- **Decision:** Deferred for MVP -- no WebSocket wiring +- **Rationale:** Inertia.js pages refresh data on every navigation, which is sufficient for MVP. The nudge notification center loads on page visit. Soketi remains configured in Docker for post-MVP activation. First post-MVP use case: real-time notification badge updates. +- **Implementation:** No implementation needed for MVP. Soketi stays in Docker Compose for future use. +- **Affects:** Post-MVP enhancement path + +### Frontend Architecture + +**D11: Filter Persistence** +- **Decision:** URL query parameters +- **Rationale:** Native Inertia.js pattern -- `router.get(url, { status: 'en_cours', assignee: 'me' })`. Filters survive page refresh, browser back/forward, and are shareable/bookmarkable. No server-side session management needed. Consistent with how Inertia handles pagination. +- **Implementation:** Filter components emit changes as URL params via `router.get()`. Controllers read filters from `Request` and apply Eloquent scopes. Default filters (e.g., Worker's "assignee = me") applied when no params present. +- **Affects:** FR41-FR44 (filtering, persistence, search) + +**D12: Document Preview** +- **Decision:** Client-side viewers (PDF.js for PDFs, native `` for images) +- **Rationale:** No server processing overhead. PDF.js is mature and handles most PDF files. Images render natively. A shadcn-vue Dialog component wraps the viewer for a modal experience. File is fetched via existing Spatie Media download route. +- **Implementation:** Vue component with `