Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
13 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Libra Law Firm is a bilingual (Arabic/English) web platform for managing client consultations, appointments, and case tracking. It serves as both a public-facing website and an internal management tool.
Domain: libra.adv.ps (production), libra.test (local)
Production Environment
- Server: h1 (
h1.elenoslabs.com,163.172.67.77) — Rocky Linux 9, ElenosLabs-owned. Not69.62.106.94— that stale IP is left over from before the migration to h1 and does not serve this app; a past.enveven hadMAIL_HOSTpointed at it by mistake (see Mail below). Full server details:wiki/prod_servers/elenoslabs/h1server.md(Jarvis workspace), § libra.adv.ps. - App user:
libra— app root/home/libra/public_html/, logs/home/libra/logs/ - PHP: 8.4 (use
php84command — there is no barephpbinary on this box; even Composer needsphp84 /usr/local/bin/composer ...) - Database: MariaDB, Redis (sessions/cache/queue)
- Queue worker:
libra-queue.service(queue:work redis) — a long-running process, see the queue note below. - Git: production was file-copied at initial migration and only properly linked to git for
the first time on 2026-09-28 (
git checkout -f -B main origin/mainafter backing up the prior state) — before that date,git pullon production did nothing (empty local branch, no commits). It is now a normal git-tracked checkout; a plaingit pullworks going forward.
MAIL_HOST must be mx1.elenoslabs.com (h1's own Postfix/Dovecot stack, MX for adv.ps) — not
mail.adv.ps, which resolves to the stale 69.62.106.94 host and times out. This caused a real
incident (2026-09-28): the workshop-registration form's emails silently never arrived because of
this wrong value, and a separate real bug in App\Services\AdminNotificationService (a Carbon
TypeError on a string throttle_minutes config value — fixed, see git history around
2026-09-28) masked the actual SMTP error in the logs the whole time it was broken. If mail seems
to silently fail again, check MAIL_HOST first, then storage/logs/laravel.log for a
TransportException.
Production Commands
All artisan commands on production require php84 prefix:
php84 artisan migrate
php84 artisan config:cache
php84 artisan optimize:clear
Deployment Checklist
# On production server (as the libra user, in /home/libra/public_html)
git pull
php84 /usr/local/bin/composer install --no-dev --optimize-autoloader # only if composer.json/lock changed
npm install && npm run build # only if frontend/Tailwind classes changed
php84 artisan optimize:clear
php84 artisan config:cache
php84 artisan route:cache
php84 artisan view:cache
# IMPORTANT: always restart PHP-FPM after ANY deploy, even a content/lang-only change.
# 2026-09-28 incident: opcache.validate_timestamps is On with revalidate_freq=2, which should
# have picked up a changed lang/*.php file within 2 seconds on its own — it didn't, and only a
# full php84-php-fpm restart made the change visible. Root cause not fully diagnosed; until it
# is, treat the restart as mandatory on every deploy, not just composer/config changes.
sudo systemctl restart php84-php-fpm
# IMPORTANT: always restart the queue worker too — it's a long-running process that holds old
# code and old .env/config values in memory until restarted, independent of the php-fpm restart.
php84 artisan queue:restart
Note: The server has both PHP 8.3 and 8.4. Always use php84 for artisan commands. Failing to restart PHP-FPM can cause cached config issues.
Development Commands
# Initial setup (install deps, create .env, migrate, build assets)
composer run setup
# Start development (server, queue, logs, vite in parallel)
composer run dev
# Run all tests
composer run test
# Run specific test file
php artisan test tests/Feature/Auth/AuthenticationTest.php
# Run tests matching a filter
php artisan test --filter=testName
# Build frontend assets
npm run build
# Format PHP code
vendor/bin/pint --dirty
# Create new Volt component with test
php artisan make:volt pages/component-name --pest
# Create model with migration and factory
php artisan make:model ModelName -mf
Architecture
Technology Stack
- Backend: Laravel 12, PHP 8.4, Laravel Fortify (auth)
- Frontend: Livewire 3, Volt (single-file components), Flux UI Free, Tailwind CSS 4
- Database: SQLite (dev), MariaDB (production)
- Testing: Pest 4
Key Directories
resources/views/livewire/- Volt single-file components (class-based pattern)resources/views/components/- Reusable Blade componentsapp/Actions/Fortify/- Authentication business logicapp/Providers/FortifyServiceProvider.php- Custom auth viewsapp/Enums/- UserType, UserStatus, ConsultationType, ConsultationStatus, PaymentStatus, TimelineStatus, PostStatusdocs/prd.md- Full product requirements documentdocs/architecture.md- Complete architecture documentdocs/ux-design.md- Design spec. Part A = the public-site redesign (approved by the client 2026-10-31, mockupdocs/ux-mockup.html); Part B = the pre-redesign system, which still governs the dashboards, emails and the Nablus formdocs/tasks/- Current task/status tracker (statuses.mdis the status source of truth). One row per epic (parent) and per story (subtask), e.g.3.md/3-4.md.docs/stories/,docs/epics/,docs/qa/gates/- Legacy. Original BMAD-agent build records (98→102 stories across 16 epics). Kept for implementation history and traceability, no longer the status source —docs/tasks/statuses.mdsuperseded them 2026-09-27..bmad-core/and.claude/commands/BMad/are the retired BMAD toolchain, also kept but unused.
Pipeline takeover (2026-09-27)
This project was built by BMAD agents and has been handed to a different pipeline (Jarvis/ElenosLabs
tooling) for any further work. docs/tasks/statuses.md is now authoritative for what's open — most
subtasks are QA Review (BMAD's "Ready for Review" with no final close-out), a couple are Draft
(never started: 3-9 guest booking request, 13-5 auth-page visual QA). Cross-check a task's own
docs/stories/story-<id>-*.md file for full history/QA notes before starting new work on it.
Services
app/Services/AvailabilityService.php- Calculates available time slots for bookingsapp/Services/CalendarService.php- Generates iCalendar files for consultationsapp/Services/CaptchaService.php- Math-based captcha for guest booking spam protection
Domain Model
Core models in app/Models/:
- User - Three types:
admin,individual(client),company(client) - Consultation - Booking requests between clients and admin
- Timeline - Case tracking for clients
- TimelineUpdate - Updates within a timeline
- WorkingHour - Admin's available hours for booking
- BlockedTime - Time slots blocked from booking
- Post - Blog/legal content articles
- Notification - User notifications
- AdminLog - Audit logging for admin actions
Route Structure
/ - Public home
/booking - Consultation booking
/posts - Public posts/blog
/language/{locale} - Language switch (ar/en)
/admin/* - Admin dashboard (requires admin middleware)
/client/* - Client dashboard
/settings/* - User settings (profile, password, appearance; 2FA route only registers if the feature is re-enabled, see below)
Middleware
admin- Ensures user is admin (EnsureUserIsAdmin)client- Ensures user is a client (EnsureUserIsClient)active- Ensures user account is active (EnsureUserIsActive)SetLocale- Sets language from session/user preference
Authentication (Fortify)
All auth is handled via Fortify with custom Volt views. Features enabled:
- Email verification, Password reset, Profile/password updates
- Two-factor authentication (QR codes + recovery codes) is disabled in production — commented
out in
config/fortify.phpat client request (2026-01-17).routes/web.phponly registerssettings/two-factorwhenFeatures::canManageTwoFactorAuthentication()is true, so the code path still exists but is currently dormant. Don't re-enable without confirming with Naser first. - Note: Public registration is disabled; admin creates all client accounts
Volt Component Pattern
This project uses class-based Volt components:
<?php
use Livewire\Volt\Component;
new class extends Component {
public string $name = '';
public function mount(): void
{
$this->name = Auth::user()->name;
}
public function save(): void
{
// ...
}
}; ?>
<div>
<!-- Blade template -->
</div>
Flux UI Components
Use Flux components when available (free edition):
avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, otp-input, profile, radio, select, separator, skeleton, switch, text, textarea, tooltip
Example: <flux:button variant="primary">Save</flux:button>
Testing Pattern
Tests use Pest with Volt::test() for Livewire components:
use Livewire\Volt\Volt;
test('profile can be updated', function () {
$user = User::factory()->create();
Volt::test('settings.profile')
->actingAs($user)
->set('name', 'New Name')
->call('updateProfileInformation')
->assertHasNoErrors();
});
User Factory States
When testing, use these factory states:
->admin()- Creates admin user->individual()- Creates individual client->company()- Creates company client->client()- Randomly creates individual or company->deactivated()- Creates deactivated user->unverified()- Creates user without email verification->withTwoFactor()/->withoutTwoFactor()- 2FA states
Notifications
Located in app/Notifications/:
- Booking:
BookingApproved,BookingRejected - Consultation:
ConsultationCancelled,ConsultationRescheduled,ConsultationReminder24h,ConsultationReminder2h - Account:
WelcomeAccountNotification,PasswordResetByAdminNotification,AccountReactivatedNotification,AccountTypeChangedNotification - Timeline:
TimelineUpdateNotification - Guest:
GuestBookingSubmitted,GuestBookingApproved,GuestBookingRejected
Business Rules
- No self-registration: Admin creates all client accounts
- Single admin: One lawyer manages the platform
- No online payments: All payments handled offline
- Cascade deletes: User deletion cascades to consultations, timelines, notifications
- Bilingual required: All user-facing text needs Arabic and English
Public-site redesign (PRD 1.4, tasks 24-x)
The public pages (home, about, posts, booking, legal) and the auth pages' type/controls follow
docs/ux-design.md Part A; the map is docs/architecture.md §6.2. Boundaries that keep the
dashboards safe:
- Redesign styles live in
resources/css/public.css, every selector underbody.site(auth:body.site-auth); its script isresources/js/public.js+resources/js/site/*, initialised onlivewire:navigated. Both load only from the public (and, CSS only, auth) layouts. - Never edit
resources/css/app.css@theme/.btn-*,components/language-toggle.blade.php, orlang/*/navigation.phpfor the redesign — the dashboards share them. New public copy goes inlang/{ar,en}/site.php. availability-calendardefaults tovariant="month"(the client dashboard uses it); the home page usesvariant="strip".pages.bookingtakescompactfor the home card; booking rules are shared and unchanged.
Gotchas found in the 24-x QA (2026-10-31) — all come from app.css/shared markup reaching into body.site:
app.csssetsoverflow-x:hiddenonbody(breaksposition:sticky) and the body font viahtml[lang] body(beatsbody.site):public.cssoverrides both (overflow-x:clip,html[lang] body.site). Any new public rule that must beatapp.cssneeds the same care.- Reveal uses the
translateproperty, nottransform, so hover lifts (transform) still work. - Flux always renders a hidden
data-flux-error, soapp.csspaints every Flux input red; auth styles override it unless the error is visible (:not(.hidden)). - A Volt template that starts with a Blade
@ifgets no Livewire root (form unbound, only visible in the browser console): wrap such templates in one root<div>. - A broad link-colour rule on auth pages must exclude
[data-test^=language-switch]: its active label is white on olive, and an olive link colour erased it. - After a change, check text against its background (contrast of active, hover and filled states) in a screenshot, not only layout metrics.
Design Requirements (from PRD)
Below: the dashboards' palette and rules. Public pages: Part A of docs/ux-design.md.
- Color scheme:
- Dark Forest Green (#2D3624) - Header/Footer backgrounds
- Warm Gold (#A68966) - CTA buttons, links, accents
- Warm Cream (#F4F1EA) - Main page background
- Forest Green (#2D322A) - Headings, body text
- Pure White (#FFFFFF) - Card backgrounds
- Olive Green (#4A5D23) - Active states
- Bilingual: Arabic (primary, RTL) / English (LTR)
- Professional, elegant aesthetic with botanical/wheat Libra logo
- See
docs/brand.mdfor complete brand guidelines