Adds two sections to the project-level CLAUDE.md: - "Strategic Direction: WHMCS Replacement" — scopes the long-arc plan (storefront, billing, provisioning, customer area, marketing site, admin RBAC, notifications, marketing email automation, future registrar / SSL / multi-tenant / partner API) so future sessions don't accidentally re-litigate decisions. - "Sister Projects (Reference)" — calls out infrastructure/ (Ezra + capacity / costs source-of-truth) and ezscale_api/ (Battlelog/ACP SaaS) as separate Gitea repos with their own CLAUDE.md, plus the catalog-data flow (website pulls hardware inventory + IP availability from infrastructure/). Also clarifies cross-repo conventions: Gitea (not GitHub), no shared DBs, design docs under docs/superpowers/specs/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
16 KiB
CLAUDE.md - EZSCALE Site
Project
EZSCALE Site — Laravel 12 application for VPS/Dedicated Server hosting management (billing, subscriptions, provisioning, customer management, SSO).
Phase Tracking (MANDATORY)
All work MUST be tracked against Gitea issues and TASKS.md. Use Gitea MCP tools (mcp__gitea__*) — never gh CLI.
- Before starting: Check the relevant Gitea issue (
mcp__gitea__list_issuesfor repoEZSCALE/website) andTASKS.mdfor current status. - While working: Update the Gitea issue with progress comments (
mcp__gitea__add_issue_comment). - After completing: Update
TASKS.mdto check off completed items and close the corresponding Gitea issue. - New tasks discovered: Create sub-issues or add items to
TASKS.mdimmediately. - Commit messages: Reference the Gitea issue number (e.g.,
Fix checkout validation (#3)).
Gitea repo: https://git.ezscale.cloud/EZSCALE/website Gitea issues: https://git.ezscale.cloud/EZSCALE/website/issues
Documentation Updates (MANDATORY)
When a phase or significant task is finished, update: TASKS.md, GitHub issues, CLAUDE.md, memory files, and PROJECT_DEVELOPMENT.md (if architecture changed).
Visual Verification (MANDATORY)
After every successful npm run build:
- Take headless Chrome screenshots of affected pages
- Compare against the EZSCALE design system (navy blue palette, Plus Jakarta Sans, custom SCSS in
resources/styles/) - Fix any visual discrepancies before considering the task complete
- Key pages: Login/Register, Marketing homepage, Admin/Account dashboards, Pricing page
Laravel App Location
The Laravel application is in website/. All artisan, composer, and npm commands run from there.
website/
├── app/
│ ├── Models/ # 29 Eloquent models
│ ├── Http/Controllers/ # Account/, Admin/, Api/V1/ controllers
│ ├── Http/Resources/ # API Resources
│ ├── Services/Billing/ # BillingServiceInterface, Stripe, PayPal, Dunning
│ ├── Events/ # Payment, Subscription events
│ ├── Listeners/ # Event handlers
│ └── Console/Commands/ # RetryProvisioning, SyncStripePrices
├── bootstrap/app.php # Middleware, exceptions, routing (Laravel 12 slim — no Kernel files)
├── database/ # 59 migrations, 24 factories, seeders
├── resources/
│ ├── ts/ # TypeScript source (Vue 3 + Inertia)
│ │ ├── types/ # Shared TypeScript interfaces
│ │ ├── Layouts/ # AccountLayout, AdminLayout, AuthLayout, MarketingLayout
│ │ ├── Components/ # Shared + marketing components
│ │ └── Pages/ # 85 Vue pages (Auth/7, Admin/41, Marketing/14, etc.)
│ └── styles/ # EZSCALE design system SCSS (8 files)
├── routes/ # web, account, admin, marketing, webhooks, api
└── tests/ # ~497 Pest tests
Tech Stack
- Framework: Laravel 12 (PHP 8.3)
- Frontend: Vue 3 + Inertia.js v2 + TypeScript (REQUIRED) + Vuetify 3 + Vite 7
- UI Theme: Custom EZSCALE design system — navy blue
#1d4ed8, Plus Jakarta Sans + JetBrains Mono (Bunny Fonts CDN), subdomain-aware theming (light for marketing, dark for admin/account) - Charts: ECharts via
vue-echarts - Utilities:
@vueuse/core - Testing: Pest 4 + PHPUnit 12 (~497 tests)
- Formatting: Laravel Pint
- Payments: Laravel Cashier (Stripe) + srmklive/paypal (PayPal)
- Database: MySQL 8.x, database driver for sessions (Redis NOT installed)
- Auth: Laravel Fortify (login, register, 2FA, password reset, email verify) + Passport (OAuth2/SSO)
- Roles: spatie/laravel-permission (admin, customer)
- Queue: Laravel Horizon
Commands
cd website
composer run dev # Start all dev servers (artisan serve + queue + pail + vite)
php artisan test --compact # Run Pest tests
npm run build # Production build
vendor/bin/pint --dirty --format agent # Format changed files
TypeScript Requirement (MANDATORY)
All frontend code MUST use TypeScript.
- All
.vuefiles must use<script setup lang="ts">(never plain<script setup>) - Props:
interface Props+withDefaults(defineProps<Props>(), {...}) - Emits:
defineEmits<{ event: [payload: Type] }>() - Explicit types for
ref<Type>(),computed<Type>(), and function return types - No
anytype — use proper interfaces or type aliases - Shared types go in
resources/ts/types/ - Inertia page props should be typed with interfaces
Design System
Colors
- Primary: Navy blue
#1d4ed8, lighter variant#3b82f6 - Subdomain theming: Marketing = light mode, Admin/Account = dark mode by default
- Theme persistence: localStorage key
ezscale-theme, read on Vuetify init
Typography
- UI font: Plus Jakarta Sans (Bunny Fonts CDN)
- Code font: JetBrains Mono (Bunny Fonts CDN)
Key Patterns
- Component ordering:
<script lang="ts" setup>then props interface, state, computed, methods, watchers - Layout system: AppSidebar (collapsible) + AppTopNavbar (sticky) + CommandPalette (Cmd+K)
- Navigation types:
NavLink,NavGroup,NavSectionTitle(seeresources/ts/@layouts/types.ts) - State management: Pinia stores (e.g.,
stores/toast.ts) - Icons: Tabler icons via
@iconify/vue(e.g.,tabler-smart-home)
Gotchas
- User::billingInvoices() — renamed from
invoices()to avoid conflict with Laravel Cashier's built-ininvoices()method. All call sites usebillingInvoices(). ThewithCountuses aliasbilling_invoices_count.
Agent Usage (MANDATORY)
Maximize use of subagents (Task tool) to reduce context usage. Main conversation should orchestrate; heavy lifting goes to agents.
- Parallel agents: Launch independent tasks as parallel agents, not sequentially in main context.
- Delegate research: Use
Exploreagents for codebase exploration and file searches. - Delegate implementation: Use
general-purposeagents for self-contained tasks. - Delegate reviews: Use
feature-dev:code-revieweragents to review code after writing it. - Delegate architecture: Use
feature-dev:code-architectorPlanagents for feature design. - Background agents: Use
run_in_background: truefor long-running tasks. - Batch similar work: 10+ file updates with the same pattern go to an agent.
- Frontend Design Skill: ALWAYS run in background (
run_in_background: true).
Headless Chrome
google-chrome --headless=new --disable-gpu --no-sandbox --screenshot=/tmp/screenshot.png --window-size=1920,1080 --virtual-time-budget=15000 "URL"
- Must use
--headless=new,--virtual-time-budget=15000, and--no-sandbox - dbus errors in output are harmless — ignore them
- Read the resulting PNG with the Read tool to view it
Code Conventions
PHP
- PSR-12, enforced by Pint
declare(strict_types=1);in all PHP files- Explicit return types and parameter type hints
- PHP 8 constructor property promotion
- Form Request classes for validation (not inline in controllers)
- Service classes for business logic (thin controllers)
- Events/Listeners for side effects (email, provisioning)
- Eloquent over raw queries; avoid
DB::, preferModel::query() - Eager loading to prevent N+1 queries
config()only, neverenv()outside config files- Named routes with
route()helper - Pest tests for all new functionality
- Database transactions for multi-step operations
- Check sibling files for conventions before creating new files
- Run
vendor/bin/pint --dirty --format agentbefore finalizing
Frontend (Vue/TypeScript)
- Use Vuetify components directly (VCard, VBtn, VTextField, etc.) — not raw HTML
- Use Inertia
Linkcomponent for navigation (not<a>tags) - Use
useForm()from@inertiajs/vue3for form submissions - Status badges: VChip with
resolveStatusColor()utilities - Pinia stores for shared state
- ECharts via
vue-echartsfor charts
Security
- All API endpoints require authentication
- Admin routes protected by role-based middleware
- CSRF on all forms (webhooks exempted via
bootstrap/app.php) - Rate limiting on auth and API endpoints
- Audit logging for admin actions and billing events
Domains
- ezscale.dev (dev) / ezscale.cloud (prod) — marketing site
- account.ezscale.dev / account.ezscale.cloud — customer dashboard
- admin.ezscale.dev / admin.ezscale.cloud — admin panel (Cloudflare Zero Trust)
- Subdomain routing configured in
bootstrap/app.phpviaRoute::domain()
Key Business Domains
- Billing — Subscriptions, invoices, payments (Stripe + PayPal), dunning, coupons
- Provisioning — VirtFusion (VPS), SynergyCP (Dedicated), Enhance (Hosting), Pterodactyl (Game) — idempotent with retry
- Customer Management — Profiles, support tickets, notifications
- Admin Panel — Dashboard, analytics, user/service management
- SSO — Single sign-on via Laravel Passport
Strategic Direction: WHMCS Replacement
This app is being built into a full WHMCS replacement for EZSCALE. Scope to track:
- Storefront / catalog — product browsing, configurable options, cart, checkout, multi-currency, quotes, coupons, tax (regional)
- Billing — subscriptions, invoices, dunning, credit balances, refunds, Stripe + PayPal (more gateways later)
- Provisioning — VirtFusion / SynergyCP / Enhance / Pterodactyl modules (idempotent, retry, suspension/termination hooks)
- Customer area — services list, KB, ticketing, login history, two-factor, affiliate dashboard, credits
- Marketing site — pricing, status page, blog, lead capture, affiliate landing pages
- Admin panel — RBAC staff, financial reports, fraud detection, dunning queue, manual invoice ops, refund flows
- Notifications — transactional email, Discord webhooks, Slack handoff to Ezra (see
infrastructure/) - Marketing email automation — drip campaigns, lifecycle/winback, broadcast announcements, segmented lists (lead/customer/lapsed), unsubscribe + preference center, deliverability hooks (Stalwart). Active planning.
- Future — domain registrar integration, SSL provisioning, reseller multi-tenancy (see
KASM_AND_MULTITENANCY.md), API for partner integrations
When planning new work, check what's already shipped (recent commits cover KB, tickets v2, multi-currency, cart, quotes, affiliates, credits, staff RBAC, fraud detection, service panels, churn prevention, login history, financial reports, configurable checkout). Gap-analyze against WHMCS feature parity before building.
Sister Projects (Reference)
The EZSCALE platform is split across three repositories, all on Gitea (git.ezscale.cloud). Read their CLAUDE.md files when touching cross-repo concerns; do not modify them from this repo.
Catalog data model: website/ owns the product catalog (SKUs, pricing, configurable options, descriptions). It pulls from various sources to populate inventory and capacity dynamically — most notably from infrastructure/ for the VPS and dedicated server lineups. Treat infrastructure/ as the source-of-truth for hardware inventory, capacity, and rack reality; treat website/ as the source-of-truth for what's for sale, at what price, and to whom.
../infrastructure/ — Ops platform & Ezra AI agent
Path: /home/andrew/local_projects/infrastructure/ · Repo: git@git.ezscale.cloud:EZSCALE/infrastructure.git
Houses everything that runs the business (not the storefront): K3s clusters (US/EU), Terraform-managed Cloudflare DNS, Ansible playbooks, the web/ Laravel 13 admin panel that hosts Ezra (the AI ops agent — capacity scoring, MRR forecasting, ticket sync, customer intelligence, investor reports), plus deep reference docs.
What website/ pulls from infrastructure/ (catalog inputs):
- VPS lineups — VirtFusion package definitions, hypervisor capacity, plan availability per region. Source:
infrastructure/web/app/Services/ApiClients/VirtFusion*+Services/Capacity/. Use to drive the/vpsproduct pages and "stock available" indicators. - Dedicated server lineups — SynergyCP inventory + the rack reality in
infrastructure/docs/reference/rack-atl.md(server SKUs, specs, current allocations). Use to drive the/dedicatedproduct pages. - Vendor costs / margins —
infrastructure/docs/reference/business-costs.md(Evocative, Hetzner, etc.). Reference when setting prices; never expose to customers. - IP availability —
infrastructure/docs/reference/ip-resources.md(ARIN vs Evocative leases). Drives whether IPv4 add-ons can be sold without a waitlist.
Other intersections (non-catalog):
- API client patterns —
infrastructure/web/app/Services/ApiClients/BaseApiClientprovides retry, rate-limit, and IPv4 DNS pinning. Port that base class here when our provisioning clients need the same hardening. - Customer intelligence —
Services/Intelligence/syncs scoring/segmentation. Website billing events should feed this; risk scores from it should drive fraud thresholds here. - Ticketing — Ezra ingests SupportPal tickets and drafts AI responses. As website/ takes over ticketing, agree on a sync contract (or have Ezra read from our DB).
- Reverse DNS / mail —
Services/Rdns/(PowerDNS) and Stalwart/SOGo. Customer rDNS requests in the account panel should flow through Ezra, not direct PowerDNS calls. - Identity — Authentik IdP at
id.ezscale.cloud. Staff SSO should integrate via SocialiteProviders/Authentik (already used ininfrastructure/web/); customers stay on Fortify + Passport. - Other reference docs —
powerdns.md,mail-server.md,authentik.md,llc-agreement.md,zfs-storage.md,uptime-kuma.md,hardware-monitoring.md.
../ezscale_api/ — Battlelog/ACP SaaS API
Path: /home/andrew/local_projects/ezscale_api/ · Repo: git@git.ezscale.cloud:EZSCALE/api.git · Endpoint: api.ezscale.cloud
Centralized Laravel 12 API at api.ezscale.cloud that fronts Battlelog (BF3/BF4/BFH) for the ACP (Adkats Control Panel) product — game-server admin SaaS sold to Battlefield community server operators. TimescaleDB-backed, single shared API key, multi-tenant ACP instances consume it.
Key intersections with website/:
- As a product — ACP is one SKU in the website catalog. When provisioning ACP, website calls into the ACP tenant orchestrator (separate from this API) but tenants in turn need the API key issued/revoked here.
- API key lifecycle — Currently a single static
EZSCALE_API_KEY. When website starts selling ACP plans with usage limits, we'll likely need per-tenant keys here — coordinate before changing the auth model. - Real-time pattern — Standalone Node.js Socket.IO + Redis pub/sub setup (
ezscale_api/socketio/) is a reusable template if website needs live admin dashboards beyond polling. - Helm/K8s deployment template —
ezscale_api/helm/ezscale-api/is the cleanest deploy reference for our eventual K8s migration of website/.
Cross-repo conventions
- Git host: Gitea at
git.ezscale.cloud(NOT GitHub). Usemcp__gitea__*tools, neverghCLI for these repos. - DB hosts: Each app has its own DB. Cross-repo data access is via HTTP APIs, never shared DB connections.
- Docs format: Each repo's
CLAUDE.mdis the canonical onboarding doc.docs/superpowers/specs/holds design docs;docs/reference/(in infrastructure) holds long-lived ops reference.
Reference Docs
TASKS.md— Task list and progress trackingPROJECT_DEVELOPMENT.md— Architecture decisions, database schema, API integrationsFEATURES.md— Feature specificationsADVANCED_FEATURES.md— Advanced feature specsKASM_AND_MULTITENANCY.md— Kasm Workspaces + reseller multi-tenancyGETTING_STARTED.md— Development setup guidedocs/— Deployment configs (deployment/), API specs (integrations/), scripts (scripts/), feature plans (superpowers/)website/CLAUDE.md— Laravel Boost guidelines (auto-generated)