7.8 KiB
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-vuefor 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@applydirective 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
@applydirectives 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']invite.config.tsto 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 bothapp.tsandChat.vue's setup functions. This ensures that missing environment variables (such asVITE_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
Mediapolymorphic model and a migration mappingmediable_idandmediable_type. - Attached and verified uploads across
Message(inline chat images/videos),Ledger(cover documents), andMutation(chore submission receipts and proof). - Built a highly reusable
.c-lightboxCSS 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:
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
nulluser_id: System messages are stored asMessagerecords with anulluser_id, cleanly distinguishing them from user-generated content. - Polymorphic Subject Linking: System messages are linked to relevant entities (e.g., a
Userwho joined a dynamic, aLedgerthat was created) via a polymorphicsubjectrelationship on themessagestable. This allows system messages on the dashboard to link directly to the relevant entity. - Seeder Refactoring: The
DatabaseSeederwas refactored to use theActivityServiceto 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:idnamerules(TEXT)created_at,updated_at
ledgers:iddynamic_idnamerules(TEXT)score(INTEGER, default 0)created_at,updated_at
mutations:idledger_iduser_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_iddynamic_idrole(e.g., 'owner', 'editor', 'viewer')
media: For handling media uploads associated with mutations.chats: For the chat streams on Dynamics and Mutations.
Development Approach
- Setup: Set up basic project structure, including models and migrations for the initial schema.
- Dynamics: Implement the creation and management of Dynamics.
- Ledgers: Implement the creation and management of Ledgers within a Dynamic.
- Mutations: Implement the core functionality of adding and suggesting mutations.
- UI: Build out the Vue components for each feature, focusing on a clean, dark, BDSM-themed aesthetic.
- Real-time: Integrate Laravel Reverb for notifications.
- Testing: Write Pest tests for all new backend functionality.
- Git: Use feature branches and make regular commits.
This is a living document and will be updated as the project progresses.