A Laravel 12 API server for Lateralzr - an application designed to improve lateral thinking skills, inspired by Edward de Bono's lateral thinking concepts and Brian Eno's Oblique Strategies card set.
Lateralzr aims to help users develop lateral thinking abilities by presenting concepts and allowing users to explore lateralized (non-linear) relationships between ideas. The API serves as the backend that provides concepts and their relationships, with LLM integration for generating new concepts and connections.
Lateral thinking, as defined by Edward de Bono, is a method of problem-solving that uses indirect and creative approaches. Unlike vertical (logical) thinking, lateral thinking involves looking at problems from new angles and finding unexpected solutions.
- Framework: Laravel 12
- Development Environment: Laravel Sail (Docker)
- Admin: Filament 5 at
/admin(Livewire/Blade; no Vite build) - Client: Expo (React Native / web) in
apps/client - AI Integration: Laravel AI SDK with Ollama (local LLM) or OpenRouter (production)
- AI Development Tools: Laravel Boost (MCP/Agent integration)
- Database: MySQL (via Sail)
- Testing: PHPUnit (API), Node test runner (Expo client)
- Docker Desktop installed and running
- Git
- Composer (optional - Sail includes it)
- Node.js (see .nvmrc; e.g. Node 20) and pnpm (package manager for the monorepo). Use NVM on the host:
nvm usein the repo root; install pnpm viacorepack enable && corepack prepare pnpm@latest --activateor pnpm.io. Optional: run the client via the Node Docker service (see Monorepo). - Ollama installed on macOS (for local LLM development)
- See 02-local-llm-and-ai-sdk_daf470d7.plan.md for installation instructions
git clone <repository-url>
cd lateralzrCopy the environment file and configure it:
cp .env.example .envUpdate .env with your configuration (Sail will handle most Docker-related settings automatically).
Start the Docker containers:
./sail up -dThe repository includes a root ./sail wrapper for ./vendor/bin/sail. You may also use the long form directly:
./vendor/bin/sail up -dDo not run Laravel backend commands directly on the host with php artisan or composer; the .env database host is configured for Sail containers.
./sail artisan key:generate./sail artisan migrate
./sail artisan db:seed # Seeds roles and, in local, the admin user test@lateralzr.comUse the development script to start Ollama (if needed) and Laravel Sail:
./dev.sh upThis will:
- Check if Ollama is running and start it if needed
- Verify the default model (
llama3.2:3b) is available - Start Laravel Sail containers
To stop services:
./dev.sh down # Stop Sail only
./dev.sh down --all # Stop Sail and OllamaThe API will be available at http://localhost (or the port configured in your .env).
This repository is a monorepo: the Laravel API (and Filament admin at /admin) lives at the root; the Expo client lives in apps/client. The package manager is pnpm (pnpm-workspace.yaml). There is no Vite / Laravel frontend asset pipeline at the root (removed with the old welcome page). Turborepo orchestrates Expo client build / dev only — root package.json scripts (dev:client, dev:all, build:all) filter or run the client workspace.
- Node version: Use the version in .nvmrc (e.g.
nvm usein the repo root) for the Expo app. Alternatively, use the optional Node Docker service (profileclient) to run client commands in a container. Ensure pnpm is available (e.g.corepack enable && corepack prepare pnpm@9.15.0 --activatein the image or use a pnpm-aware image), then from the repo root:
docker compose --profile client run --rm node sh -c "pnpm install --frozen-lockfile && pnpm turbo run dev --filter=client". - Run the API: from the repo root,
./sail up -d(or./vendor/bin/sail up -d, or./dev.sh up); see Installation. Filament admin needs nopnpm/vitebuild — it is PHP/Livewire plus committedpublic/js/filamentassets. The Concept Graph Explorer loadswindow.cytoscapefrom the unpkg script registered inAdminPanelProvider(not an npm/Vite bundle). - Run the client: from the repo root,
cd apps/client && pnpm exec expo start, then choose web (w), iOS (i), or Android (a). Or use Turbo:pnpm turbo run dev --filter=client(from root). - Independent deployment: In CI, deploy only the API when changes are outside
apps/client/**; deploy only the client when changes are underapps/client/**.
- Prerequisites: Node version per .nvmrc (NVM on host or Node Docker service); optionally Xcode (iOS) / Android Studio (Android) for native runs.
- Environment: Set
EXPO_PUBLIC_API_URL(e.g. inapps/client/.env) to the API base URL (no trailing slash). Example:EXPO_PUBLIC_API_URL=http://localhost. - See Expo Documentation for building and deploying the app.
- Test URL params (Expo web / Critiquito):
canonicalConcept,localizedConcept,onlyWithMedia,complexity,laterality(complexity/laterality are this load/session only; they do not persist), plus existinglocale. Example:https://lateralzr-client.vercel.app/?canonicalConcept=mushroom&locale=es&complexity=5. Docs: apps/client/TEST-URL-PARAMS.md.
A Filament 5 admin panel is available at /admin for quick inspection and management of data (e.g. Concepts, Users).
- URL:
http://localhost/admin(or your app URL +/admin). Production:https://api.lateralzr.com/admin. - Auth: Login required. Access is restricted to users with the
super_adminrole (Spatie Laravel Permission). - Local seed user (created only when
APP_ENV=local):- Email:
test@lateralzr.com - Password:
!12345678
After running./sail artisan migrateand./sail artisan db:seed, this user exists in local and can log in to the backoffice.
- Email:
- Production: do not use
make:filament-useralone (it does not assignsuper_admin). Create or promote an admin with./bin/artisan users:create-filament-admin {email} --name="..." --password='...'or./bin/artisan users:promote-filament-admin {email}. See DEPLOYMENT.md.
# Start containers
./sail up -d
# Stop containers
./sail stop
# View logs
./sail logs
# Run Artisan commands
./sail artisan <command>
# Process concept graph jobs manually
./sail artisan queue:work --queue=default --timeout=300
# Verify AI provider/model (OpenRouter in prod)
./sail artisan ai:ping --provider=openrouter --dry-run
./sail artisan concepts:prefetch --provider=openrouter --starts=creativity --count=10
# Run Composer commands
./sail composer <command>
# Run tests
./sail test
# Run Pint
./sail pint
# Access container shell
./sail shell
# Access Tinker
./sail tinkerSee Testing for the default mocked suite.
GET /api/hello
Returns a simple health check response.
Response:
{
"message": "Hello, Lateralzr API is running!",
"status": "ok"
}GET /api/media?url=
Allowlisted reverse proxy for concept card images. The Expo web client loads Wikimedia (and other allowlisted) mediaUrls through this endpoint so display does not depend on third-party CORS. Native clients should keep requesting the original mediaUrl directly (Wikimedia is fine there; browsers are not).
Only https URLs on upload.wikimedia.org with a raster image extension are fetched. SVG is rejected. Responses are cached by browsers (Cache-Control) and the route is throttled.
POST /api/concepts/relationships
Returns a prefetched graph neighborhood from the database (start + nodes + edges + meta). It does not call the LLM. Cold start: omit start (or send an empty body) to let the API choose a random concept that already has edges. Missing graphs return 404 (No prefetched graph found for this start yet.).
Test/dev filters (same names as the Expo web query params; see apps/client/TEST-URL-PARAMS.md):
start/seed/localizedConcept— localized (or any-locale) termcanonicalStart/canonicalConcept— language-neutralcanonical_key(use withlocale)onlyWithMedia— only concepts that have amediaUrllocale—enorescomplexity— optional integer 1–5; prefers terms generated at that label-density tier. Omitted requests keep the unfiltered walk (backward compatible).laterality— optional integer 1–5; biases neighborhood edge orderlimit/depth/minStrength— neighborhood size (defaults 100 / 2 / 0)
Request:
{
"start": "creativity",
"locale": "en",
"complexity": 2
}start(seed/localizedConceptaliases) — optional; when omitted, the API picks a random concept that already has relationships.complexity— optional integer 1–5; when set, prefers a term at that stored complexity. Omitted requests do not force a default complexity on the walk.
Response:
{
"data": {
"start": { "id": 1, "label": "creativity" },
"nodes": [
{
"id": 1,
"label": "creativity",
"shortDescription": "...",
"complexity": 2,
"wikiUrl": "...",
"mediaUrl": null,
"locale": "en",
"media": [{ "url": "...", "kind": "image", "license": "CC0" }],
"degree": 1
}
],
"edges": [
{ "id": 10, "from": 1, "to": 2, "strength": 0.72, "laterality": 3 }
],
"meta": { "depth": 2, "limit": 100, "minStrength": 0.0, "hasMore": false, "locale": "en", "complexity": 2 }
},
"status": "success"
}Note: This read path does not need Ollama. Use Local LLM Setup and ./sail artisan concepts:prefetch (plus the queue worker) to grow the graph.
lateralzr/
├── app/ # Laravel application code
│ ├── Http/
│ │ └── Controllers/ # API controllers
│ └── Models/ # Eloquent models
├── apps/
│ └── client/ # Expo client (React Native + TypeScript, Expo Router)
├── config/
│ └── concepts.php # Default seed concepts (cold start)
├── database/
│ ├── migrations/ # Database migrations
│ └── seeders/ # Database seeders
├── routes/
│ └── api.php # API routes
├── tests/ # Test suite
│ └── Feature/ # Feature tests
├── turbo.json # Turborepo pipeline (Expo client tasks)
├── pnpm-workspace.yaml # pnpm workspace (apps/*)
├── pnpm-lock.yaml # pnpm lockfile
├── .nvmrc # Node version (e.g. 20)
├── .cursor/plans/ # Historical project plans (not current setup)
└── compose.yaml # Docker Compose (Sail) configuration
This project uses Ollama running natively on macOS for local LLM development, integrated via Laravel AI SDK. This allows for cost-free development and testing of concept relationship generation.
-
Install Ollama (if not already installed):
brew install ollama # Or download from https://ollama.com/download -
Pull the default model:
ollama pull llama3.2:3b
-
Start development environment:
./dev.sh up
-
Grow the graph (LLM + queue; not the public read API):
./sail artisan concepts:prefetch --starts=creativity --count=10 ./sail artisan queue:work --queue=default --timeout=300
Then exercise the prefetched read API:
curl -X POST http://localhost/api/concepts/relationships \ -H "Content-Type: application/json" \ -d '{"start": "creativity"}'
The default model (llama3.2:3b) can be changed via the OLLAMA_MODEL environment variable in .env:
OLLAMA_MODEL=llama3.1:8b # For higher quality (slower)
OLLAMA_MODEL=llama3.2:1b # For faster responsesPrefetch and grow the graph with Ollama (or OpenRouter). The public POST /api/concepts/relationships read path does not call the LLM. See Testing for the mocked default suite.
For detailed setup instructions, troubleshooting, and model recommendations, see .cursor/plans/02-local-llm-and-ai-sdk_daf470d7.plan.md.
This project uses Laravel Boost for AI agent integration. Boost provides:
- MCP Tools: Deep insight into application structure, database, routes
- AI Guidelines: Laravel-specific coding guidelines for AI agents
- Documentation Search: Access to Laravel ecosystem documentation
Configuration files:
boost.json- Boost configuration.cursorrules/- Cursor-specific agent rules (if using Cursor)
- Create a feature branch
- Make your changes
- Write or update tests
- Ensure all tests pass:
./sail test - Submit a pull request
Default API tests are mocked. They do not call Ollama, Wikipedia, or Wikimedia.
# Laravel (from repo root; use Sail locally)
./sail test
# Specific file
./sail test tests/Feature/ApiHealthTest.php
# Coverage
./sail test --coverage
# Seeded DB graph neighborhood (runs in the default suite)
./sail test --group=db-graph
# Expo client
pnpm --filter client test
pnpm --filter client typecheckEnrichment lives in app/Services/Enrichment/*. There is no live-HTTP smoke group.
CI runs php artisan test (respects phpunit.xml), vendor/bin/pint --test on the API job, and the Expo typecheck plus unit tests.
This project emphasizes test-driven development. New features should include unit tests for business logic and feature tests for API endpoints.
For production deployment to the Hetzner VPS with Docker Compose and Traefik, see DEPLOYMENT.md.
Automated deployment (recommended):
- GitHub Actions workflow (
.github/workflows/deploy-prod.yml) - Triggers on push to
mainor manual dispatch - Deploys to
/home/cgonzalez/lateralzrvia SSH
Manual deployment:
- SSH to VPS and run
./bin/deploy-prod - Includes dirty-tree guard, git fetch/pull, build, migrate, optimize
Quick specs:
- Production stack:
docker-compose.prod.yml - Uses shared Traefik reverse proxy and MySQL from
gonzalezrico_platformnetwork - Public host: https://api.lateralzr.com
- Services: nginx, app (PHP-FPM), queue worker, scheduler (
php artisan schedule:work— no VPS crontab) - On the VPS, run artisan with
./bin/artisan <command>(never hostphp artisan) - Isolated from gonzalezrico: Deployment never touches the gonzalezrico compose stack
[To be determined]
- Laravel Documentation
- Laravel AI SDK Documentation
- Laravel Sail Documentation
- Laravel Boost Documentation
- Expo Documentation
- Expo Skills (GitHub) – Cursor users can add Expo Skills as a Remote Rule (Settings → Rules & Command → Project Rules → Add Rule → Remote Rule (GitHub) →
https://github.com/expo/skills.git) for better agent support when working on the client app. - Ollama Documentation
- Edward de Bono - Lateral Thinking
- Oblique Strategies - Brian Eno