# CLAUDE.md

## Quick Reference

**Stack**: PHP 8.x, Slim 2.6.2, Twig 1.44.8, MySQL (multi-store), Redis, Ably, Twilio/Vonage

**CSS Framework**: Bootstrap 5.3.3 with custom design tokens (`public_html/css/admin/`)

**Commands**:
```bash
./test.sh                           # Run all tests
./test.sh --testsuite unit          # Run unit tests only
./test.sh --testsuite integration   # Run integration tests only
./test.sh --coverage                # Run with coverage report
./test.sh --stan                    # Run tests + PHPStan analysis
./deploy.sh                         # Test + deploy
cd userfrosting && composer install

# CSS Build Commands
php userfrosting/conductor build-css           # Development build
php userfrosting/conductor build-css --minify  # Production build (generates version hash)
php userfrosting/conductor build-css --watch   # Watch mode for development

# Database Migrations
php userfrosting/conductor run

# Static Analysis (PHPStan)
cd userfrosting && ./vendor/bin/phpstan analyse                    # Run PHPStan analysis
cd userfrosting && ./vendor/bin/phpstan analyse --memory-limit=2G  # With increased memory
cd userfrosting && ./vendor/bin/phpstan analyse src/BuyerKiosk/Feature/  # Analyze specific path

# TaskEngine Commands
php userfrosting/bin/task worker:start                    # Start a single worker
php userfrosting/bin/task worker:start --queues=high,default,low  # Start worker with specific queues
php userfrosting/bin/task worker:manager --status         # Check worker pool status
php userfrosting/bin/task queue:status                    # View queue depths
php userfrosting/bin/task queue:status --detailed         # View detailed queue status
php userfrosting/bin/task scheduler:run                   # Run scheduler (once per minute via cron)
php userfrosting/bin/task job:list                        # List all job definitions
php userfrosting/bin/task job:dispatch <job-name>         # Manually dispatch a job
php userfrosting/bin/task job:dispatch <job-name> --store=ou00  # Dispatch per-store job

# TaskEngine Local Workers (macOS launchd)
launchctl list | grep buyerkiosk                          # Check running workers
launchctl load ~/Library/LaunchAgents/com.buyerkiosk.taskengine-worker*.plist    # Start all workers
launchctl unload ~/Library/LaunchAgents/com.buyerkiosk.taskengine-worker*.plist  # Stop all workers
tail -f logs/task-worker.log                              # View worker logs

# Docker Development
./docker-dev.sh up                    # Start all containers
./docker-dev.sh down                  # Stop all containers
./docker-dev.sh shell                 # Shell into web container
./docker-dev.sh logs                  # Tail web logs
./docker-dev.sh test                  # Run tests in Docker
./docker-dev.sh migrate               # Run migrations in Docker
./docker-dev.sh mysql                 # MySQL CLI in Docker
./docker-dev.sh help                  # Show all Docker commands
```



**Entry**: `public_html/index.php` → `userfrosting/initialize.php`

**TypeNum**: Store identifier pattern `[a-z][a-z]\d+` (e.g., `ou00`, `pa00`)

---

## Architecture

### Multi-Store
- **Central DB**: `kiosk_users` (auth), `kiosk_buykiosk` (shared data)
- **Store DBs**: Named by typeNum (e.g., `kiosk_ou00`)

### Break Policy Module (Spec 049)
Per-store configurable break-policy engine. Pure-function evaluator drives paid/unpaid classification + compliance violations.

**File map:**
- `src/BuyerKiosk/Scheduling/Services/BreakPolicy/BreakPolicyEvaluator.php` — pure-PHP rules engine (NO I/O)
- `src/BuyerKiosk/Scheduling/Services/BreakPolicy/BreakPolicyFeatureFlag.php` — `isEnabled($typeNum)` + `isApplicable($typeNum, $shiftId)` (WIW exclusion via `schedulingProvider === 'wiw'`)
- `src/BuyerKiosk/Scheduling/Services/BreakPolicy/DTO/*` — 5 readonly DTOs
- `src/BuyerKiosk/Scheduling/Models/{BreakPolicyPreset,BreakPolicyPresetRule,StoreBreakPolicy,StoreBreakPolicyRule,BreakComplianceLog}.php`
- `src/BuyerKiosk/Scheduling/Repositories/{BreakPolicyPresetRepository,BreakPolicyRepository,BreakComplianceRepository}.php`
- `src/BuyerKiosk/Scheduling/Controllers/{BreakPolicyController,ComplianceController}.php`
- `routes/groups/{break-policy,compliance}.php` + `routes/admin/{break-policy,compliance}.php`
- `templates/themes/default/admin/scheduling/{break-policy,compliance-dashboard}.html`
- `public_html/js/scheduling/{BreakPolicyPage,ComplianceDashboard}.js`
- `public_html/css/admin/modules/break-policy.css`

**Feature flag:** `kiosk_buykiosk.stores.breakPolicyEnabled` (TINYINT 0/1, default 0). Off = engine never runs, all legacy classification paths preserved.

**WIW exclusion:** `BreakPolicyFeatureFlag::isApplicable` returns false when `Store::$schedulingProvider === 'wiw'` (ADR-5). Legacy `wiwEnable=1` alone does NOT exclude — many BK-native stores have a WIW token configured but use native scheduling.

**Dev tooling:**
- `scripts/break-policy-flag.php <typeNum> <on|off>` — flip flag for pilot/rollback
- `scripts/break-policy-diff-replay.php <typeNum> <from> <to>` — read-only diff vs `TimesheetController::buildDayBreakdown`
- `scripts/apply-049-{store-policy-schema,flsa-backfill}.php <typeNum>` — targeted per-store migration helpers (bypass dev=0 filter)

**Runbook:** `docs/runbooks/break-policy-yearly-refresh.md` covers preset version updates.

### Database Conventions
- **Column naming**: camelCase (e.g., `eventId`, `startDate`, `integrationType`)
- **Table naming**: camelCase for new tables (e.g., `eventTemplates`, `eventIntegrations`)
- **Foreign keys**: camelCase with descriptive names (e.g., `templateId`, `sourceEventId`)


### Key Directories
```
userfrosting/
├── src/BuyerKiosk/       # PSR-4 autoloaded classes (239 total)
│   ├── Core/             # Domain models: Store, Buy, BuyQueue, Customer
│   │   ├── Controllers/  # Core controllers: Account, Admin, User
│   │   ├── Loyalty/      # Loyalty program classes
│   │   └── Robot/        # Automation classes
│   ├── Workbook/         # Workbook/Daybook system
│   ├── Backstock/        # Inventory management
│   ├── Cash/             # Cash management
│   ├── SMS/              # SMS services (Twilio, Vonage)
│   └── Compatibility/    # Legacy class aliases
├── models/
│   └── BaseModel.php     # DB connections, queue helpers, auth utils
├── routes/               # Route definitions
├── templates/themes/default/  # Twig templates
└── patches/              # PHP 8.x fixes
└── migrations/           # Database Migrations 
│   └── input             # Migration JSON Files


public_html/
├── index.php             # Entry point
├── css/admin/            # Design system (tokens.css, admin-theme.css, modules/)
├── css/vendor/           # Bootstrap 5.3.3 CSS
├── js/vendor/            # Bootstrap 5 JS, Masonry.js
├── js/workspace/         # Modern JS modules
└── view/common/js/       # Legacy JS (buyQueue.js)

tests/
├── Unit/                 # Unit tests by module
├── Integration/          # Integration tests
├── Fixtures/             # Test data factories
├── Mocks/                # Mock classes for external dependencies
│   ├── PdoMockBuilder    # Fluent PDO mocking
│   ├── TwilioMock        # Twilio SMS mock
│   ├── VonageMock        # Vonage SMS mock
│   ├── RedisMock         # In-memory Redis mock
│   └── StoreMock         # Store object factory
├── Support/              # Test case extensions
└── DatabaseTestCase.php  # Base class with transaction isolation

logs/
├── error_log                         # General PHP Error Log
├── buyerkiosk_com.php.error.log      # Site-specific PHP Error Log (check this first for 500s)

### Core Patterns
```php
// Store access
$storeController = new \BuyerKiosk\StoreController($typeNum);
$store = $storeController->getStore();
$db = dbConnectByName($store->getDbName());

// Permission check
if (!$app->user->checkAccess('uri_store_settings')) $app->notAuthorized();
if (!$app->user->checkStoreGroup($typeNum)) $app->notAuthorized();
```

### Routes
| Pattern | Purpose |
|---------|---------|
| `/:typeNum/` | Store operations |
| `/admin/:typeNum/` | Admin pages |
| `/api/` | API endpoints |
| `/api/mobile/` | Mobile app API |

---

## Key Files Quick Reference

### Backend
| File | Purpose |
|------|---------|
| `models/BaseModel.php` | DB connections, queue helpers, auth utils |
| `src/BuyerKiosk/Core/Store.php` | Store entity (config, integrations) |
| `src/BuyerKiosk/Workbook/Controllers/WorkbookPageController.php` | Workspace pages |
| `src/BuyerKiosk/ComebackCash/` | Comeback Cash promotional coupon system |
| `routes/workbook/pages.php` | Workspace routes |
| `routes/api.php` | API routes |
| `routes/groups/comeback-cash-pos.php` | Comeback Cash POS API routes |

### Frontend
| File | Purpose |
|------|---------|
| `templates/themes/default/workspace/workspace.html` | SPA container |
| `templates/themes/default/workspace/partials/queue/` | Queue templates |
| `public_html/css/workspace/workspace.css` | Workspace styles |
| `public_html/js/workspace/workspace.js` | Workspace init |
| `public_html/view/common/js/buyQueue.js` | Legacy queue JS |

### Design System
| File | Purpose |
|------|---------|
| `public_html/css/admin/tokens.css` | CSS custom properties (colors, spacing, typography) |
| `public_html/css/admin/admin-theme.css` | Bootstrap 5 customizations |
| `public_html/css/admin/modules/` | Page-specific styles (backstock, comeback-cash, workbook) |
| `public_html/css/admin/vendor/` | DataTables theme overrides |
| `public_html/css/admin/version.txt` | Build hash for cache-busting |
| `templates/themes/default/admin/style-guide.html` | Live component reference page |

---

## Development Notes

### Adding Features
1. Create class in `userfrosting/src/BuyerKiosk/Feature/` with proper namespace
2. Route in `userfrosting/routes/`
3. Controller in `src/BuyerKiosk/Feature/Controllers/`
4. Permission hook if needed
5. Template in `templates/themes/default/`
6. Use typeNum + `checkStoreGroup()` + `dbConnectByName()`

### PSR-4 Autoloading
- All classes under `BuyerKiosk\` namespace auto-load via Composer
- No manual includes needed - just use the class
- See `docs/patterns/psr4-autoloading.md` for details
- See `docs/patterns/namespace-structure.md` for namespace map

### Handlebars in Twig
Wrap `{{` in `{% raw %}{% endraw %}`

### CSS & Styling
- **Framework**: Bootstrap 5.3.3 (NOT Bootstrap 3)
- **Style Guide**: `/admin/style-guide` - live component reference with copy-paste code
- **CSS Variables**: Use `var(--primary-600)`, `var(--space-4)`, etc. from `tokens.css`
- **Class Migration**: `.panel` → `.card`, `.label-*` → `.badge`, `data-toggle` → `data-bs-toggle`
- **Build**: Run `php userfrosting/conductor build-css --minify` after CSS changes
- **Icons**: Font Awesome 6 only (v4-shims for backward compatibility)
- **Masonry**: Use `data-masonry='{"percentPosition": true}'` + include `partials/masonry-init.html`

### Bootstrap 5 Modals (MANDATORY — endemic codebase issue)
Every admin/workspace page renders inside `<body><div id="wrapper"><div id="page-wrapper">…`. That wrapper structure creates a stacking context that traps Bootstrap 5 modals behind their backdrop (gray screen, unclickable). **Whenever you add a `<div class="modal fade">` to a template, OR you read a template that contains one, IMMEDIATELY invoke the `bootstrap5-modal-backdrop-stacking` skill** and apply the body-relocation fix in the page's JS init. Also: use `bootstrap.Modal.getOrCreateInstance(el)` instead of `new bootstrap.Modal(el)` to avoid duplicate-backdrop bugs. This is not optional — modals will silently break without these patterns.

### Live Debugging
Chrome DevTools MCP at https://dev2.buyerkiosk.com (not Browserbase)

### Integrations
Twilio/Vonage, QuickBooks, Shopify, FiveStars, WhenIWork, Ably, OpenAI

### Mobile Apps (In Development)

| App | Path | Purpose |
|-----|------|---------|
| BuyerKiosk Live | `../buyerkiosk-live-flutter` | Admin/Manager App |
| BuyerKiosk Team | `../buyerkiosk-team` | Team Scheduling/Clock-in |

### Inter-Agent Communication (Mobile App Coordination)

**CHECK THESE FILES when starting a session:**
- `docs/api/mobile-agent-requests.md` - Requests from Team app
- `docs/api/live-agent-requests.md` - Requests from Live app

Mobile app agents write API requests there when they need:
- Endpoint specifications not in existing docs
- New endpoints for mobile features
- Clarification on payloads, responses, or error codes

**When responding to requests:**
1. Update status to `in-progress` while working
2. Fill in the "Backend Response" section with details
3. Include file:line references when helpful
4. Move completed requests to the "Completed" section
5. If API changes are needed, implement them and note the changes

**WRITE UPDATES TO MOBILE APPS when you:**
- Add new mobile API endpoints
- Make breaking changes to existing mobile APIs
- Add new fields mobile should consume
- Deprecate endpoints mobile is using
- Fix bugs that change mobile-facing behavior

**Write to:**
- `../buyerkiosk-team/docs/backend-api-updates.md` - Team app updates
- `../buyerkiosk-live-flutter/docs/backend-api-updates.md` - Live app updates

Use this format:
```markdown
### [YYYY-MM-DD] [Brief Title]
**Type**: `new-endpoint` | `breaking-change` | `enhancement` | `deprecation`
**Affects**: [Which features]
#### Summary
[What changed]
#### Details
[Endpoint details, payloads, etc.]
#### Mobile Action Required
- [ ] [Action item]
```

**Key Files:**
| App | Incoming Requests | Outgoing Updates |
|-----|-------------------|------------------|
| Team | `docs/api/mobile-agent-requests.md` | `../buyerkiosk-team/docs/backend-api-updates.md` |
| Live | `docs/api/live-agent-requests.md` | `../buyerkiosk-live-flutter/docs/backend-api-updates.md` |


!!!!!! MOST IMPORTANT !!!!!!!
DO NOT MODIFY TABLES DIRECTLY. USE OUR MIGRATION SYSTEM. CHECK userfrosting/conductor. DO NOT LOSE THIS INSTRUCTION ON COMPACT.
USE SYNCFUSION COMPONENTS OVER BOOTSTRAP OR CUSTOM IMPLEMENTATIONS WHEN POSSIBLE
BUYS TABLE IN STORE DATABASE IS NAMED buyQueue NOT buys
We don't want to use the employee ID at all. The store level employee table is deprecated. We use the global uf_users users table now 