Development Guide
This guide covers local setup, testing, and coding standards.
Contributing
Contributions welcome — features, fixes, tests, and docs.
- Setup: Follow Quick Start below
- Branch: Create a feature branch from
dev - Code: Make small, focused commits
- Test: Run backend and frontend tests (see Testing)
- PR: Open a pull request to
dev
For detailed git workflow, branching strategy, and conflict resolution, see Git Workflow.
Fast Evaluator Path
If you are opening this repo to assess it quickly rather than contribute to it, use this shorter path:
Run the seeded app
bash./utils/deploy.sh --seedOpen the main surfaces
- App: http://localhost:8000
- Admin: http://localhost:8001
- API Docs: http://localhost:8000/api/documentation
- Architecture: Architecture
Use the seeded accounts
- Admin:
admin@catarchy.space / password - User:
user1@catarchy.space / password
- Admin:
Run the quick review command
bashbun run review:quick
Looking at the app
Use ./utils/look.sh to inspect pages with Playwright Chromium. It signs in as demo@catarchy.space / password, opens the requested path, and prints absolute PNG paths. Open those images when checking a UI change.
./utils/look.sh /finance
./utils/look.sh /finance "Add expense"
LOOK_DEVICE="iPhone 15" ./utils/look.sh /
LOOK_AUTH=guest ./utils/look.sh /login
LOOK_WIDTH=1280 LOOK_HEIGHT=800 LOOK_FULL_PAGE=1 ./utils/look.sh /The first screenshot shows the page. Each following argument clicks a button by its exact accessible name and takes another screenshot. These are real clicks against the running app, so saving or deleting changes its data. Duplicate button names cause an error instead of choosing a match.
Install dependencies with cd frontend && bun install --frozen-lockfile, then install the browser with bun x playwright install chromium. Start the local stack with ./utils/deploy.sh --seed if it has not been set up. After UI changes, run ./utils/deploy.sh before taking screenshots: port 8000 serves the frontend and PHP baked into the Docker image, not the working tree. Worktrees share this stack, so coordinate deployments.
Each invocation starts a fresh browser session in English and creates a unique meomaimoi-look-* directory in the system temporary directory. LOOK_OUT chooses its parent directory. Previous screenshots are never deleted. The tool blocks service workers to avoid stale cached pages and runs independently of E2E setup; it does not seed, reset data, or start a server. Mobile devices use Chromium with the selected viewport, touch settings, and user agent, not native Safari.
For another seeded account, set LOOK_AUTH=user, LOOK_EMAIL, and LOOK_PASSWORD in your environment. Use PLAYWRIGHT_BASE_URL to choose another running app origin. LOOK_SETTLE_MS adjusts the extra wait after network activity settles, with a default of 1000 milliseconds. ./utils/look.sh --help lists all options. A failed login, navigation, or click exits nonzero; each saved image also prints the final page URL so redirects are visible.
Quick Start
If your shell does not recognize vp, the repo is still usable with bun run ... from frontend/ because those scripts already delegate into Vite+. You can either use that fallback or add your local Vite+ install directory to PATH.
New to the repo? Follow these steps to get running:
Run the app
bash./utils/deploy.shNote: The backend automatically creates
.envfrom.env.examplewhen you run anyphp artisancommand if it doesn't exist. You'll see a helpful message to runphp artisan key:generate.Tip: Use
./utils/deploy.sh --skip-buildfor faster deployments when you've already built the Docker images and just need to restart containers or run database migrations.Access the app
- Main App: http://localhost:8000
- Admin Panel: http://localhost:8001 (admin@catarchy.space / password)
- API Docs: http://localhost:8000/api/documentation
- Project Docs: http://localhost:8000/docs (VitePress)
Admin port settings (
ADMIN_HOST_BIND,ADMIN_HOST_PORT,VITE_ADMIN_URL) belong in the repo-root.env.ADMIN_URLbelongs inbackend/.env.If port 8001 still shows the main app after an upgrade, unregister the stale service worker for
localhost:8001in browser DevTools → Application → Service Workers.Optional: Enable HTTPS for local dev (single compose)
bash# Set in backend/.env APP_ENV=development ENABLE_HTTPS=true # Generate self-signed certificates (one-time setup) # Note: script will NOT overwrite existing certs; use --force to regenerate ./utils/generate-dev-certs.sh # Deploy using the single entry point ./utils/deploy.sh # Access via HTTPS # https://localhost (browser will show security warning - click "Advanced" → "Proceed") # https://localhost:8001 # https://localhost/docsWhy HTTPS in dev?
- Test features requiring secure context (Service Workers, Web Crypto API, etc.)
- Match production behavior more closely
- Test HTTPS-specific security headers
Production note: Production deployments use
docker-compose.ymlonly (no dev override). HTTPS is handled by reverse proxy (nginx/caddy/traefik) with proper certificates.Generate API Client
If you change the backend API (@OA annotations), you must regenerate the frontend types and hooks:
bash# Run from root to sync both backend and frontend vp run api:generateIf
vpis not available in your shell yet, use:bashbun run api:generateAlternatively, run separately:
bashcd backend && php artisan l5-swagger:generate cd ../frontend && vp run api:generateCI/Commit Guardrail To ensure your generated code matches the current backend attributes, run:
bashvp run api:checkShell fallback:
bashbun run api:check(Exits with code 1 if there are uncommitted changes to generated files).
Deployment: The
./utils/deploy.shscript automatically regenerates both the OpenAPI spec and the frontend client during the pre-build stage to ensure the production image is always consistent.Usage: Refer to
frontend/src/api/generated/for the output. Prefer using the generated hooks (useGetPets,usePostPets, etc.) over manual Axios calls for full type safety.See API Conventions for more details on full-stack typesafety.
Test Users (Seeded Data)
- Super Admin: admin@catarchy.space / password
- Admin: user1@catarchy.space / password
- Regular Users: 3 users with factory-generated names/emails / password
Admin Features
- User Impersonation: Click 👤 icon in Users table to impersonate any user
- Stop Impersonating: Use navbar indicator or admin panel to return
- User Ban/Unban: Ban users to put them in read-only mode (view-only, no writes); unban to restore full access
- User Storage Visibility: Open
http://localhost:8001/users/:idto view storage used and storage limit for that user - Storage Limits Config: Open
http://localhost:8001/system-settingsto configure default vs premium storage ceilings
Testing
Backend (Pest/PHPUnit)
Run backend tests on the host, from a checkout with composer install done. The app container is built with composer install --no-dev, so PHPUnit is not in it. The suite needs a Postgres on 127.0.0.1:5432; the local stack's db service is one.
# Run all tests in parallel against this checkout's own test database
./utils/test-backend.sh
# The same, spelled out. Fine in the primary checkout; in a worktree it shares
# meo_mai_moi_testing with every other checkout, so use the wrapper there.
cd backend && php artisan test --parallel --processes=4
# Run tests without parallel execution
cd backend && php artisan test --no-parallel
# Run specific test suites
cd backend && php artisan test --parallel --processes=4 --testsuite=Feature
cd backend && php artisan test --parallel --processes=4 --testsuite=UnitRunning tests locally (outside Docker):
If you need to run tests on your local machine:
cd backend
# First time only - .env will be auto-created from .env.example
composer install
php artisan key:generate
# Filament admin assets under public/{js,css,fonts}/filament are gitignored
# and are published automatically by composer's post-autoload-dump hook.
# Configure your local database settings in .env, then run tests
php artisan test --parallel --processes=4Frontend (Vitest)
cd frontend
# Run all tests
vp test
# Run one test file
vp test run src/pages/messages/MessagesPage.test.tsx
# Coverage report
vp run test:coverageEnd-to-End (Playwright)
Playwright E2E tests live under frontend/e2e/.
For this project, the default E2E runner (frontend/scripts/e2e-test.sh) manages Docker services (db, backend, mailhog) and database seeding automatically before running Playwright.
Offline-mode E2E coverage lives in frontend/e2e/offline-mode.spec.ts (pet create/edit/delete, medical record create, and habit day check-in queue/replay). Integration coverage for the same journeys without a full browser is in frontend/src/offline/offline-mode.integration.test.tsx. See Offline Mode for the feature matrix and test-layer guidance.
The checked-in Playwright config now defaults to a single worker for stability with the seeded shared accounts. Override it with PLAYWRIGHT_WORKERS only when you intentionally want to trade some determinism for speed.
Quick start:
# From frontend/
cd frontend
vp run e2e # prepare Docker services/data, then run headless
vp run e2e:direct # run headless against an already prepared stack
vp run e2e:ui # interactive UI against an already prepared stack
vp run e2e:report # open the last HTML reportThe setup-backed e2e command is the safe default. Both e2e:direct and e2e:ui skip service startup and database seeding, so use them only after the Docker app stack and MailHog are ready.
Important when debugging against http://localhost:8000: Playwright is exercising the Docker-served app, not a live Vite dev server. If you change frontend code that is bundled into the backend-served SPA, rebuild the frontend and then rebuild the backend container image so the running app actually serves the new assets:
cd frontend
bun run build
cd ..
docker compose up -d --build backendA plain docker compose restart backend is enough for backend-only PHP changes, but not for frontend asset changes that are baked into the image.
If Playwright browsers are missing on your machine, install them once:
cd frontend
vp exec playwright install chromiumStatic Analysis & Quality Gates
Code Quality (Formatting)
Run Laravel Pint to automatically format code according to PSR-12 and Laravel conventions.
# From the backend directory
cd backend
./vendor/bin/pintPHPStan (Backend Type Analysis)
PHPStan enforces type safety and catches bugs at write-time using static analysis. Currently configured at Level 5.
Run analysis:
cd backend
composer phpstanDeptrac (Architecture Layer Enforcement)
Deptrac prevents architectural violations by enforcing allowed dependencies between layers.
Run analysis:
cd backend
composer deptracAsset Management
PWA Assets
PWA icons, screenshots, and manifests are maintained as explicit source assets in frontend/public and mirrored to backend/public when they need to be served from Laravel's public root. Do not regenerate the web manifests from an icon-only script: the manifests include install metadata such as id, screenshots, shortcuts, categories, theme colors, and the offline start URL.
Every app entry point links the stable /site.webmanifest URL. Theme changes update page metadata without switching the manifest. The canonical manifest's colors are fixed, so the installed splash does not follow the user's theme. Keep the light, dark, and /build/ manifest paths available for old installations. All manifest responses use no-cache, must-revalidate.
bun run manifest:version stamps launcher and shortcut icon URLs with a SHA-256 content hash of each PNG and mirrors all three manifests into backend/public. It runs during builds and icons:generate; app version bumps alone do not change icon URLs. Run it after replacing PNGs manually, then run vp test to check the stamps. Root icon files retain their one-day HTTP cache policy.
See docs/logo-update.md for the SVG source family, icon generation commands, platform fallbacks, and visual verification checklist.
The frontend ships separate light and dark web manifests. Theme switching between them is handled by the app-owned runtime in frontend/src/lib/theme-runtime.ts, while the backend SPA shell applies the initial resolved theme before hydration so the manifest, theme-color, and color-scheme metadata stay in sync from first paint onward. Those manifests intentionally do not lock orientation, so the installed PWA can rotate with the device.