ledgerrz/DECISIONS.md

107 lines
7.8 KiB
Markdown

# Decisions
This document outlines the decisions made during the development of the Ledgerrz application.
## Core Concepts
* **Dynamic:** A relationship between two or more users. This will be the core container for interactions.
* **Ledger:** A score-tracking entity within a Dynamic. The name "Ledger" will be used instead of "Score" to establish a more distinct domain language.
* **Mutation:** An event that modifies a Ledger. It can be a direct modification or a suggestion requiring approval.
* **Participant:** A user's role within a Dynamic. This will be used to manage permissions.
## Technology Stack
* **Backend:** Laravel
* **Frontend:** Vue.js with Inertia.js
* **Real-time:** Laravel Echo via `@laravel/echo-vue` for notifications and real-time updates.
* **Testing:** Pest for PHP tests.
* **Styling:** **BEM (Block, Element, Modifier)** methodology. Replaced verbose inline Tailwind classes in custom HTML templates with semantic BEM classes (e.g., `.c-chat`, `.user-info__avatar`), and mapped them to CSS rules using Tailwind v4's `@apply` directive inside scoped `<style>` blocks. This preserves the clean, dark, BDSM-themed aesthetic while significantly improving markup readability.
## Architectural & Integration Decisions
### 1. Style Architecture Shift to BEM & Modern CSS Nesting
To improve front-end maintainability, we transitioned the core reusable layout and page components away from raw utility-class markup. Styles are now strictly encapsulated within Vue Single File Component (SFC) `<style scoped>` blocks (or dedicated BEM component files under `/resources/css/components/`).
* **CSS Nesting Standard (Critical Learning):** Standard CSS/PostCSS nesting (unlike SASS) **does not** support class suffix concatenation (e.g. `.block { &__element { ... } }` is invalid). All nested selectors must declare the full class name explicitly (e.g. `.block { .block__element { ... } }`), compiling to standard descendant selectors (`.block .block__element`). Suffix nesting was refactored across all 12 component stylesheets to ensure native CSS browser and LightningCSS compatibility.
* **Encapsulation:** Used Tailwind v4 `@apply` directives inside these blocks, referencing our central stylesheet (`@reference "../../css/app.css"`) to pull in custom themes and variables cleanly.
* **Third-Party Safety:** Third-party shadcn-vue library primitives (located in `components/ui/`) were kept intact to preserve vendor update integrity.
### 2. BelongsToMany Pivot Loading caveat
When accessing pivot attributes on relationship models on the front-end (e.g. `participant.pivot.role`), the relationship definition in the Eloquent model **must** explicitly call `->withPivot('role')`. Omitting this will cause the pivot object to fail loading role attributes silently in Javascript, breaking role-based template gates. We added this to `App\Models\Dynamic::participants()`.
### 3. Robust Real-time Echo Fallback & Deduplication
To address potential initialization and bundling errors under local-development:
* **Module Deduplication:** Configured `resolve.dedupe: ['@laravel/echo-vue']` in `vite.config.ts` to ensure exactly one instance of the Echo plugin and configuration state is shared across main and lazy-loaded bundles.
* **Defensive Initialization:** Added checks using `echoIsConfigured()` and configured fallback connection parameters (e.g., `'mock-key'`) in both `app.ts` and `Chat.vue`'s setup functions. This ensures that missing environment variables (such as `VITE_REVERB_APP_KEY`) do not trigger fatal Pusher initialization crashes that block component rendering, allowing the websocket to fail silently (which is correct when Reverb is not running locally).
### 4. Controller Authorization Fix
We imported the `Illuminate\Foundation\Auth\Access\AuthorizesRequests` trait directly into `LedgerController.php`. This fixes the 500 Internal Server Error when loading the ledger page, which was caused by `LedgerController` calling `$this->authorize('view', ...)` without inheriting the required method from the Laravel 11 base `Controller`.
### 5. Polymorphic Multiple-Media Attachment Support
We added a polymorphic attachments system allowing any database model to attach multiple photos and videos cleanly:
* Created `Media` polymorphic model and a migration mapping `mediable_id` and `mediable_type`.
* Attached and verified uploads across `Message` (inline chat images/videos), `Ledger` (cover documents), and `Mutation` (chore submission receipts and proof).
* Built a highly reusable `.c-lightbox` CSS component for modal previews of both image and video uploads, avoiding duplication in individual modules.
### 6. Cryptographically Secure Dynamic Invitation System
We implemented a secure Dynamic Invitation flow to allow owners to invite new members to a Dynamic under a specific role:
* **Signed temporary URLs:** Invitation links dispatched in mailable emails (`DynamicInvitationMail.php`) use Laravel's temporary signed URLs, expiring in 7 days, to prevent link tampering.
* **Intended-Email Check Gate:** To prevent link hijacking, the accept gate strictly checks that the authenticated user's email matches the exact email specified on the invitation:
```php
if ($request->user()->email !== $invitation->email) {
abort(403, 'This invitation was sent to a different email address.');
}
```
* **Access Control:** Access to invitations and creation is fully protected under Owner-only authorization checks.
### 7. Centralized Activity Service for System Messages
We created `app/Services/ActivityService.php` to centralize the creation of system messages and activities.
* **System Messages as `null` user_id**: System messages are stored as `Message` records with a `null` `user_id`, cleanly distinguishing them from user-generated content.
* **Polymorphic Subject Linking**: System messages are linked to relevant entities (e.g., a `User` who joined a dynamic, a `Ledger` that was created) via a polymorphic `subject` relationship on the `messages` table. This allows system messages on the dashboard to link directly to the relevant entity.
* **Seeder Refactoring**: The `DatabaseSeeder` was refactored to use the `ActivityService` to generate all system messages, ensuring consistency.
## Initial Database Schema
I will start with a basic schema and evolve it as I build features.
* `users`: Standard Laravel users table.
* `dynamics`:
* `id`
* `name`
* `rules` (TEXT)
* `created_at`, `updated_at`
* `ledgers`:
* `id`
* `dynamic_id`
* `name`
* `rules` (TEXT)
* `score` (INTEGER, default 0)
* `created_at`, `updated_at`
* `mutations`:
* `id`
* `ledger_id`
* `user_id` (author)
* `type` (e.g., 'addition', 'subtraction')
* `amount` (INTEGER)
* `description` (TEXT)
* `status` (e.g., 'approved', 'pending', 'rejected')
* `created_at`, `updated_at`
* `participants`: (Pivot table for users and dynamics)
* `user_id`
* `dynamic_id`
* `role` (e.g., 'owner', 'editor', 'viewer')
* `media`: For handling media uploads associated with mutations.
* `chats`: For the chat streams on Dynamics and Mutations.
## Development Approach
1. **Setup:** Set up basic project structure, including models and migrations for the initial schema.
2. **Dynamics:** Implement the creation and management of Dynamics.
3. **Ledgers:** Implement the creation and management of Ledgers within a Dynamic.
4. **Mutations:** Implement the core functionality of adding and suggesting mutations.
5. **UI:** Build out the Vue components for each feature, focusing on a clean, dark, BDSM-themed aesthetic.
6. **Real-time:** Integrate Laravel Reverb for notifications.
7. **Testing:** Write Pest tests for all new backend functionality.
8. **Git:** Use feature branches and make regular commits.
This is a living document and will be updated as the project progresses.