npx agentspec-cli install 714d15f6-5a01-4d3d-86c1-245411907712 --file-type claude-md→ .claude/rules/
# CatLab Drinks — Agent / AI Instructions
## Project Overview
CatLab Drinks is a bar automation / POS system built with Laravel + Vue.js.
It has three frontend apps (Manage, POS, Client) sharing one Laravel backend.
Online at [https://drinks.catlab.eu](https://drinks.catlab.eu).
---
## Additional AI Context Documents
The `.ai/` directory contains supplementary documentation with detailed implementation notes,
framework quirks, and architectural decisions. **Always read all files in `.ai/` before making
changes**, as they contain critical information not duplicated here.
Current documents:
| File | Contents |
|------|----------|
| `.ai/charon-bulk-delete-instructions.md` | Bulk delete support in Charon v1.8.0 — route format, request/response shape, and a known `mergeOptions()` type-hint bug with middleware arrays |
| `.ai/deployment.md` | Heroku + Dokku dual-deployment setup — buildpacks, Procfile fallback, app.json, Dockerfile |
| `.ai/signed-order-urls.md` | Signed remote order URLs — HMAC-SHA256 signature algorithm, token structure, integration guide with code examples |
---
## Building & Testing
### Frontend
```bash
npm install # Install JS dependencies
npm run dev # Development build
npm run watch # Watch mode
npm run production # Production build
```
Build uses Laravel Mix (`webpack.mix.js`). Output goes to `public/res/`.
> **Critical:** Always use `npm install` to install dependencies — **never `npm update`** unless you are
> explicitly changing `package.json`. Running `npm update` rewrites `package-lock.json` with newer
> dependency versions and can introduce unintended breaking changes. If you are not editing
> `package.json`, use `npm install` only.
> **Never commit changes to `package-lock.json`.** Even `npm install` can modify `package-lock.json`
> (e.g. due to different npm versions or platform differences). Always revert any changes to
> `package-lock.json` before committing. Run `git checkout -- package-lock.json` after `npm install`.
### Backend
```bash
composer install # Install PHP dependencies (use --ignore-platform-reqs if PHP version mismatches)
php artisan migrate # Run database migrations
php artisan route:list # Verify routes are registered correctly
php artisan route:list --path=<prefix> # Filter routes by path prefix
```
No automated test suite exists currently. Manual testing is required.
The lock file requires PHP ~8.1 or ~8.2; use `--ignore-platform-reqs` on newer PHP versions.
### JavaScript Tests
```bash
npx jest # Run Jest tests (KeyManager, Card, etc.)
npx vitest run # Run Vitest tests (route/view tests)
```
---
## Architecture
### Three Frontend Apps
| App | URL | Entry Point | Auth Method |
|---------|----------|-----------------------------------|-----------------|
| Manage | /manage/ | resources/manage/js/app.js | OAuth (auth:api)|
| POS | /pos/ | resources/pos/js/app.js | Device token |
| Client | /order/ | resources/clients/js/app.js | Public/token |
### Two API Layers
| API | Prefix | Routes File | Auth |
|--------------|--------------|------------------------------------------|-----------------|
| Management | /api/v1/ | app/Http/ManagementApi/V1/routes.php | OAuth (auth:api)|
| Device (POS) | /pos-api/v1/ | app/Http/DeviceApi/V1/routes.php | auth:device |
### Shared Namespace (`app/Http/Shared/V1/`)
Controllers and resource definitions that are used by **both** APIs live in the Shared namespace.
Each API creates a thin extending class in its own namespace for route resolution (because Charon
resolves controller names relative to the route collection's `namespace` setting).
**Pattern for sharing a controller between APIs:**
1. Put the full implementation in `App\Http\Shared\V1\Controllers\FooController`
2. Create `App\Http\ManagementApi\V1\Controllers\FooController extends \App\Http\Shared\V1\Controllers\FooController {}`
3. Create `App\Http\DeviceApi\V1\Controllers\FooController extends \App\Http\Shared\V1\Controllers\FooController {}`
4. Register routes in both `routes.php` files using `\App\Http\{Api}\V1\Controllers\FooController::setRoutes($routes)`
**Currently shared controllers:** EventController, MenuController, CategoryController, OrderController, OrderSummaryController
**Currently shared resource definitions:** OrderResourceDefinition, OrderSummaryResourceDefinition, OrderSummaryItemResourceDefinition
Resource definitions in the Shared namespace may reference definitions from ManagementApi (e.g., `MenuItemResourceDefinition`) — cross-namespace references are an established pattern.
### REST Framework: CatLab Charon
The backend uses CatLab Charon for REST API scaffolding:
- **Resource Definitions** (`app/Http/*/V1/ResourceDefinitions/`) define field visibility, writeability, and validation
- **Controllers** use `ChildCrudController` or `CrudController` traits from Charon
- Routes are registered via `static setRoutes(RouteCollection $routes)` on each controller
- The `childResource()` method auto-generates index/view/store/edit/destroy routes
- Use `'only'` parameter to restrict which routes are generated
- Route collections have a `namespace` property; controller action strings (e.g. `'OrderController@index'`) are resolved relative to it
**Charon edit/update flow** (used by `CrudController::edit()` and custom update endpoints):
1. Parse request body via `$this->resourceTransformer->fromInput($resourceDefinitionFactory, $writeContext, $request)`
2. Validate with `$inputResource->validate($writeContext, $entity)`
3. Apply writeable fields to entity via `$this->toEntity($inputResource, $writeContext, $existingEntity)`
4. Save via `$this->saveEntity($request, $entity)` which wraps in a DB transaction and calls `beforeSaveEntity()`/`afterSaveEntity()` hooks
5. Return via `$this->createViewEntityResponse($entity)`
**`beforeSaveEntity` / `afterSaveEntity` hooks:**
- Override these in controllers to add custom logic (e.g., validation, side effects)
- When using the CrudController trait, alias it: `use CrudController { beforeSaveEntity as traitBeforeSaveEntity; }`
- Call `$this->traitBeforeSaveEntity($request, $entity, $isNew)` to preserve the trait's default behavior
- Use `$entity->isDirty('field')` in `beforeSaveEntity` to check if a field changed before save
- Use `$entity->wasChanged('field')` in model `updated` events to check if a field actually changed after save
**Model events for side effects:**
- Prefer Eloquent model events (`created`, `updated`, etc.) over controller-level side effects for logic that should trigger regardless of which API endpoint or context modifies the model
- Register model events in the model's `boot()` or `booted()` method
- Use `wasChanged()` (not `isDirty()`) in `updated` events, since `isDirty()` is cleared after save
### Adding a New API Endpoint
1. Create or update the ResourceDefinition (in Shared if used by both APIs, otherwise in the specific API namespace)
2. Create or update the Controller using Charon traits (in Shared if used by both APIs)
3. If shared, create thin extending classes in both ManagementApi and DeviceApi namespaces
4. Register routes in the controller's `setRoutes()` method
5. Register routes in `routes.php` for each API that needs the endpoint
6. Create/update the Policy in `app/Policies/`
7. Register the policy in `AuthServiceProvider`
### Authorization Policies
Policies live in `app/Policies/` and extend `BasePolicy`.
**Key pattern — `$allowDevices` flag:**
- `BasePolicy::isMyEvent($user, $event, $allowDevices = false)` checks if the user/device belongs to the event's organisation
- When `$allowDevices = true`, both `User` and `Device` principals are accepted (uses `isDeviceOrUserPartOfOrganisation()`)
- When `$allowDevices = false` (default), only `User` principals are accepted (uses `isMyOrganisation()`)
- **Read-only** POS endpoints should pass `$allowDevices = true`
- **Write/admin** endpoints should use the default `false`
**Device self-management:**
- `DevicePolicy::view()` and `DevicePolicy::edit()` allow a device to view/edit itself (`$user->id === $device->id`)
- This enables POS devices to update their own settings (category filter, order preferences) via `PUT /pos-api/v1/devices/current`
- Always call `authorizeEdit($request, $entity)` or `authorizeView($request, $entity)` in device API endpoints
**Example:** `EventPolicy::orderSummary()` passes `$allowDevices = true` so POS devices can view sales summaries.
### Frontend Services
Frontend services extend `AbstractService` (`resources/shared/js/services/AbstractService.js`):
- Set `entityUrl` (singular resource path) and `indexUrl` (list path)
- Inherits `index()`, `get()`, `create()`, `update()`, `delete()` methods
- API base URL is `CATLAB_DRINKS_CONFIG.API_PATH`
- For Manage: `/api/v1`
- For POS: `/pos-api/v1`
- The same service classes are used by both Manage and POS — the base URL determines which API is called
### Frontend Routing
Vue routes are defined inline in each app's `app.js` (no separate router files).
Shared views from `resources/shared/js/views/` are imported by both apps.
**POS-specific views** live in `resources/pos/js/views/` (e.g., Headquarters, Menu, Authenticate, Events).
**Manage-specific views** live in `resources/manage/js/views/` (e.g., Devices, Events, Menu).
Both apps share the same route names for common features (e.g., `sales`, `summary`, `summary-names`).
This allows shared components to use `router-link` with named routes that work in either app context.
**Router mode:** Use **hash history** (not web history) when running in Cordova / the native app wrapper.
---
## Deployment
### Overview
The app supports two deployment targets that coexist in the same repo:
| Target | Build method | Web process |
|--------|-------------|-------------|
| **Heroku** | Buildpacks (`heroku/nodejs` + `heroku/php`) | `heroku-php-apache2 public/` |
| **Dokku** | `Dockerfile` (`thecodingmachine/php:8.1-v5-slim-apache`) | `apache2-foreground` (via image entrypoint) |
### How it works
- **`Procfile`** contains the `web:` command with a bash fallback:
```
web: bash -c 'if command -v heroku-php-apache2 &> /dev/null; then heroku-php-apache2 public/; else apache2-foreground; fi'
```
On Heroku (buildpacks), `heroku-php-apache2` exists and is used. On Dokku (Dockerfile), it doesn't exist so `apache2-foreground` is used instead.
- **`release:`** in the Procfile runs `php artisan migrate --force` on every deploy (supported by both Heroku and Dokku).
- **`app.json`** sets `"stack": "heroku-22"` and declares both buildpacks so "Deploy to Heroku" button works automatically without any manual `heroku stack:set` or `heroku buildpacks:add`.
- **`Dockerfile`** is used by Dokku only. Heroku ignores it entirely when using buildpacks.
### Heroku buildpack order
The Node.js buildpack **must run before** the PHP buildpack so that `npm run prod` (via the `heroku-postbuild` script in `package.json`) compiles frontend assets before PHP's composer install. This is declared in `app.json`'s `buildpacks` array and must also be set on existing Heroku apps:
```bash
heroku buildpacks:add --index 1 heroku/nodejs
```
### Key files
| File | Purpose |
|------|---------|
| `Procfile` | Web process + release task for both Heroku and Dokku |
| `app.json` | Heroku "Deploy to Heroku" button config (stack, buildpacks, env vars, addons) |
| `Dockerfile` | Dokku build (uses `thecodingmachine/php:8.1-v5-slim-apache`) |
| `heroku.yml` | **Deleted** — not used; would conflict with buildpack stack |
---
## Device Management
### POS Authentication Flow
POS devices don't use OAuth — they use device access tokens stored in localStorage:
- `catlab_drinks_device_pos_uid` — device UUID
- `catlab_drinks_pos_api_identifier` — stripped API host (note: legacy typo in key name)
- `catlab_drinks_pos_api_url[identifier]` — full API base URL
- `catlab_drinks_pos_access_token[identifier]` — Bearer token
### Pairing Flow
1. Admin creates connect request → gets token + QR code
2. Device scans QR / pastes URL → calls `POST /api/v1/device-connect`
3. New device shows pairing code → admin enters it via `POST /device-connect-requests/{token}/pair`
4. Device receives access token → stored in localStorage
### 401 Handling
The POS Axios interceptor (`resources/pos/js/bootstrap.js`) clears all localStorage keys
and reloads on 401, returning the device to the pairing screen.
---
## NFC Card Signing
### Key Management
- Each POS device generates an ECDSA P-192 key pair stored encrypted in localStorage
- Private key is encrypted with the device secret (from server) via AES
- Public key is uploaded to the server and must be **approved** by an admin before the device can sign cards
- Approved public keys are downloaded and stored in the `KeyManager`'s `publicKeysByDeviceId` map
### V1 Card Format
Payload layout (85 bytes total):
```
version(1) + deviceId(3) + balance(4) + txCount(4) + timestamp(4) + prevTx(5×4=20) + discount(1) + signature(48)
```
- Signed data: `version + deviceId + payload + cardUid`
- Signature: ECDSA P-192, 48 bytes (24 r + 24 s)
### KeyManager Injection
The `KeyManager` must be set on `NfcReader` **before** cards are scanned via `nfcReader.setKeyManager(keyManager)`.
The reader injects it into each `Card` object before calling `parseNdef()`.
Call `cardService.initializeKeyManager(uid, id, secret)` and `cardService.loadPublicKeys(keys)` during device boot.
---
## Key Models
| Model | Table | Purpose |
|----------------------|------------------------|----------------------------------|
| Device | devices | POS terminal registration |
| DeviceAccessToken | device_access_tokens | Auth tokens for devices |
| DeviceConnectRequest | device_connect_requests| Pairing session state |
| Event | events | Bar/event configuration |
| MenuItem | menu_items | Products for sale |
| Order | orders | Customer orders |
| OrderItem | order_items | Line items within an order |
| Organisation | organisations | Multi-tenant grouping |
| User | users | Admin accounts |
---
## Common Patterns
- Vue components use Bootstrap-Vue (`b-*` components)
- Vue 3 compatibility mode via `@vue/compat`
- Shared components live in `resources/shared/js/`
- Services handle API communication, views handle UI
- The Settings view (`resources/shared/js/views/Settings.vue`) is shared between apps
- POS Events page has an Actions dropdown per event with links to Sales overview and Order history
- Manage Events page has a richer Actions dropdown including edit, delete, menu editing, attendees, etc.
- When adding POS access to an existing Manage-only feature: move controller + resource definitions to Shared, create thin stubs in both API namespaces, update the policy's `$allowDevices` flag, add frontend links
---
## Project Structure
```
app/
├── Http/
│ ├── ManagementApi/V1/ # Management API (controllers, resource definitions, routes)
│ ├── DeviceApi/V1/ # POS Device API
│ ├── Shared/V1/ # Shared controllers and resource definitions used by both APIs
│ │ ├── Controllers/ # OrderController, OrderSummaryController, EventController, etc.
│ │ └── ResourceDefinitions/# OrderResourceDefinition, OrderSummaryResourceDefinition, etc.
│ └── Middleware/
├── Models/ # Eloquent models
├── Policies/ # Authorization policies
└── Providers/
resources/
├── manage/js/ # Manage app (Vue components, services, views)
├── pos/js/ # POS app (Vue components, services, views)
├── clients/js/ # Client order app
├── shared/js/ # Shared Vue components and services
│ ├── services/ # AbstractService, EventService, SettingService, etc.
│ ├── nfccards/ # NFC card reader integration
│ │ ├── models/Card.ts # Card model (parse/serialize, signing)
│ │ ├── crypto/KeyManager.ts# ECDSA key management
│ │ ├── nfc/NfcReader.ts # Base NFC reader (injects KeyManager into Cards)
│ │ ├── nfc/RemoteNfcReader.ts # Socket.IO NFC reader
│ │ ├── nfc/AppNfcReader.ts # Capacitor app NFC reader
│ │ └── CardService.ts # High-level card service (transactions, signing, license)
│ └── views/ # Shared views (Sales, SalesSummary, Cards, Settings, etc.)
└── sass/ # SCSS stylesheets
```
CatLab Drinks - Development Instructions
CatLab Drinks is a bar automation / POS system built with Laravel + Vue.js. It has three frontend apps (Manage, POS, Client) sharing one Laravel backend.
by AgentHub Crawler
Kubernetes Agent Development Guide
AI assistant instructions for the Kubernetes Agent project. This is an experimental project - prioritize simplicity over backwards compatibility.
by AgentHub Crawler
EdgeCrab — Development Guide
Instructions for AI coding assistants and developers working on the edgecrab codebase.
by AgentHub Crawler
AI Agent Instructions for Dev Tools
Sign in to leave a comment.
Browser-first, privacy-friendly developer toolkit. All tools run client-side—no uploads, no accounts. Tools include JSON/YAML/CSS parsers, image converters, color utilities, SVG optimizer, and more.
by AgentHub Crawler