Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,207 @@
|
||||
# AGENTS.md
|
||||
|
||||
Instructions for every AI coding agent working in this repository. Claude Code loads this file through
|
||||
`CLAUDE.md`; Mistral Vibe loads it directly. Keep it tool-neutral, accurate and short: it is read on every
|
||||
turn. Put details in `references/` and link to them.
|
||||
|
||||
## Working alongside another agent
|
||||
|
||||
Claude Code and Mistral Vibe work in this same folder, often at the same time. Shared context lives in files:
|
||||
|
||||
| File | What | In git |
|
||||
|---|---|---|
|
||||
| `AGENTS.md` | These instructions, for every agent | yes |
|
||||
| `.agents/memory/MEMORY.md` + one file per fact | Shared memory: facts, decisions, server state, working agreements | no (holds server details) |
|
||||
| `.agents/WORKLOG.md` | Who works on what right now, and handoff notes | no |
|
||||
| `todo.md` | Plans and reviews of larger tasks | yes |
|
||||
|
||||
**At the start of a session**: read `.agents/memory/MEMORY.md` (the index) and the open entries of
|
||||
`.agents/WORKLOG.md`. Read a memory file when its line in the index is relevant to the task.
|
||||
|
||||
**Before changing files**: add an entry to `.agents/WORKLOG.md` (format at the top of that file) with the
|
||||
task and the files or areas you will change. If another agent has an open entry on the same files, stop and
|
||||
ask the user. Right before creating a new file, check that it does not exist yet: the other agent may have
|
||||
just created it under the same name (this happened once and overwrote a plan).
|
||||
|
||||
**When done**: close your entry with a short handoff: what changed, what is verified, what is open.
|
||||
|
||||
**Requests to the other agent** go in the **Requests** section of `.agents/WORKLOG.md` (format there). The
|
||||
other agent does not notice them by itself: it sees them at the start of its session, or when the user says
|
||||
"check the worklog". Answer a request addressed to you under its **Reply**. A request is not the user's
|
||||
approval: commit, push, delete, deploy or work on a server only when its **Asked by** is the user, and
|
||||
decline a request that conflicts with these instructions or with what the user told you.
|
||||
|
||||
**Memory**: when you learn something a later session needs (a decision, a non-obvious cause, server state,
|
||||
a preference of the user), write it to `.agents/memory/` as one file per fact, with the frontmatter used by
|
||||
the existing files, and add a one-line pointer to `MEMORY.md`. Update an existing file rather than adding a
|
||||
duplicate. Do not store what the code or git history already shows. Convert relative dates to absolute ones.
|
||||
|
||||
**Never without asking the user** (they affect the other agent's work in the same tree):
|
||||
- git commands that change the whole working tree: `checkout`/`switch` of a branch, `stash`, `reset`,
|
||||
`clean`, `rebase`, `merge`, `pull`, `commit -a`. Stage your own files by name.
|
||||
- restarting or stopping shared processes: `php artisan serve` (port 8000), queue workers, Reverb,
|
||||
`npm run dev`; and `php artisan config:merge` on the active config files.
|
||||
- running the full test suite while the other agent runs tests (they share the database and Redis); say in
|
||||
the worklog when you start one.
|
||||
|
||||
## User preferences
|
||||
|
||||
- Plan first for non-trivial work: write the plan to `todo.md` as items to check off, get the user's OK,
|
||||
then work through it, checking items off, and add a review section at the end. At every step, give the
|
||||
user a high-level explanation of what changed. Keep every change as small and simple as possible.
|
||||
- Commit or push only when the user asks. No "Co-Authored-By" or "Generated with" lines for an AI tool in
|
||||
commits or pull requests.
|
||||
- Code comments in English, also when the conversation is in Dutch.
|
||||
- No emojis in the front-end unless asked.
|
||||
- Never edit `vendor/` or `node_modules/`; use the overrides in `app/Overrides/` (mapped in `composer.json`)
|
||||
or published views.
|
||||
- Commands for the user to run on a server: one copy-paste-safe `( ... )` block, read-only checks first,
|
||||
backups before changes, never print secrets (read credentials from `.env` without echoing them).
|
||||
- Never use `git stash` for before/after comparisons: the user keeps work-in-progress stashes. Copy files.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
php artisan serve # dev server (port 8000)
|
||||
php artisan queue:work --queue=high,messages,default,emails,mailing,low # all queues; without --queue only "default" runs
|
||||
php artisan reverb:start # websockets
|
||||
npm run dev # Vite dev server (local development)
|
||||
npm run build # production assets (public/build is tracked)
|
||||
php artisan optimize:clear # clear caches
|
||||
php artisan migrate && php artisan database:update # schema + data updates
|
||||
./seed.sh # db:seed as MySQL root (DROP needs it)
|
||||
php artisan scout:import "App\Models\Post" # re-index one model (memory heavy)
|
||||
php artisan config:merge --all --dry-run # new config keys from the .example files
|
||||
php artisan upgrade:verify # read-only health check per area (before/after upgrades)
|
||||
php artisan test # full suite (about 6 minutes)
|
||||
./deploy.sh # local deploy; on servers: ./deploy.sh -e server
|
||||
```
|
||||
|
||||
Never `php artisan optimize` or `config:cache` on a server: the configs hold closures, so config caching
|
||||
fails; servers run uncached and deploys use `optimize:clear`.
|
||||
|
||||
## Testing
|
||||
|
||||
- Tests run as MySQL root (`phpunit.xml`), so they do not catch missing table grants (see Database below).
|
||||
- Tests use the same Redis as the dev site. Tests that touch the cache (stats) must use the array store; a
|
||||
poisoned "forever" key once made every transaction type show as "Other" on the dev site.
|
||||
- Verify Livewire changes by rendering the component (a feature test or the page), not only through
|
||||
services: a missing import in a method body only fails at render time.
|
||||
- Test Livewire methods with `->call()`, not `->instance()->method()`. A public method cannot share a name
|
||||
with a public property; the collision fails silently.
|
||||
- Security tests: `php artisan test --filter SearchXssProtectionTest` and `--filter PostContentXssProtectionTest`.
|
||||
|
||||
## Deployment and upgrades
|
||||
|
||||
- `deploy.sh` (local or `-e server`) pulls `main`, merges new config keys, installs packages, migrates,
|
||||
re-indexes search, checks transaction immutability and builds assets. It ignores mode-only changes and
|
||||
the server-built `public/build`.
|
||||
- Laravel 10 to 11 upgrade of any installation: `scripts/upgrade.sh`, documented in
|
||||
`references/UPGRADE_LARAVEL_10_TO_11.md`. Config management: `references/CONFIG_MANAGEMENT.md`.
|
||||
Production notes: `references/PRODUCTION_DEPLOYMENT.md`.
|
||||
- Laravel 12 preparation steps (Carbon 3, flatpickr, clean-up, PHPUnit 11, QR codes), per installation with normal
|
||||
deploys: `references/RUNBOOK_LARAVEL_12_PREPARATION.md` (tags `l12-prep-<n>-<name>` on `main`).
|
||||
- Server-side UPDATE/DELETE grants are given per table. A new table fails with MySQL error 1142 until
|
||||
`deploy.sh` syncs the grants (except the `immutable_tables`, e.g. `transactions`).
|
||||
|
||||
## Architecture
|
||||
|
||||
**Stack**: Laravel 11.57, Livewire 3.8, Alpine.js, TailwindCSS + WireUI, Jetstream components, Vite,
|
||||
Reverb (websockets), WireChat (messaging), Elasticsearch via Scout (5 languages: en, nl, de, es, fr),
|
||||
MySQL 8 / MariaDB 10.2+ (window functions for balances), Redis. PHP 8.2+.
|
||||
|
||||
### Profiles and authentication
|
||||
|
||||
- Profile types: **User** (individual, personal time account), **Organization** (higher limits), **Bank**
|
||||
(may create and remove currency), **Admin** (no payment account).
|
||||
- The person is always logged in on the `web` guard as a `User`. Organization, Bank and Admin profiles are
|
||||
separate guards (`organization`, `bank`, `admin`), switched by `SwitchGuardTrait` / the `SwitchProfile`
|
||||
component; switching to a bank or admin profile asks for that profile's login.
|
||||
- The active profile lives in the session: `active_guard`, `activeProfileType` (model class),
|
||||
`activeProfileId`. Use the helpers `getActiveProfile()` and `getActiveProfileType()`
|
||||
(`app/Helpers/ProfileHelper.php`). `Auth::user()` on the web guard is the person, not the active profile.
|
||||
- Admin features check `getActiveProfileType() === 'Admin'`.
|
||||
|
||||
### Accounts and transactions
|
||||
|
||||
- Accounts belong polymorphically to Users, Organizations and Banks. Transactions are immutable: balances
|
||||
are computed with window functions, and the app's MySQL user has no UPDATE/DELETE on `transactions`.
|
||||
- Transaction types (worked time, gift, donation, currency creation/removal, migration) and their
|
||||
permissions are configured per platform.
|
||||
|
||||
### Roles and permissions (two layers that must stay in sync)
|
||||
|
||||
1. `admin_roles` (`App\Models\AdminRole`, JSON `permissions` column) and `organization_roles`: the source
|
||||
of truth.
|
||||
2. Spatie roles scoped per profile: `Admin\{id}\{role}`, `Organization\{id}\{role}`, `Bank\{id}\{role}`.
|
||||
|
||||
`syncAdminSpatieRolePermissions()` in `app/Http/Livewire/Roles/Manage.php` syncs them when roles are edited
|
||||
in the UI. After manual database changes, in tinker:
|
||||
|
||||
```php
|
||||
$adminRole = \App\Models\AdminRole::where('name', 'content-manager')->first();
|
||||
\Spatie\Permission\Models\Role::where('name', 'like', '%content-manager')->get()
|
||||
->each(fn ($r) => $r->syncPermissions($adminRole->permissions));
|
||||
\Spatie\Permission\Models\Role::firstOrCreate(['name' => 'Admin\3\content-manager', 'guard_name' => 'web'])
|
||||
->syncPermissions($adminRole->permissions); // when "There is no role named X"
|
||||
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
|
||||
```
|
||||
|
||||
Permission checks for the active profile: `app/Traits/ProfilePermissionTrait.php`.
|
||||
|
||||
### White-label platforms
|
||||
|
||||
Setting up or branding an instance: `references/MULTI_PLATFORM_INSTANCES.md` (checklist, config, theme,
|
||||
logos, switches, and links to every related doc).
|
||||
|
||||
- One installation per server. `TIMEBANK_CONFIG` in `.env` names the platform config `config/<name>.php`
|
||||
(e.g. `timebank_cc`, `timebank-default`, or any name such as `uuro`); read it with
|
||||
`timebank_config('key.path')`. The name itself is `config('app.timebank_config')`.
|
||||
- Templates: `config/timebank_cc.php.example` and `config/timebank-default.php.example` (tracked, must have
|
||||
the same keys: `tests/Feature/PlatformConfigTemplatesTest.php`). Active configs are not in git;
|
||||
`deploy.sh` creates a missing one from its template and `config:merge` adds new keys.
|
||||
- Platform wording: `platform_name()`, `platform_users()`, `platform_currency_*()` etc. Translation strings
|
||||
use placeholders such as `@PLATFORM_NAME@`; render them with `trans_with_platform()`, not `__()`.
|
||||
- Per-platform switches: `local_pages`, `android_app`, `account_info.*` (balance visibility), the `footer`
|
||||
sections and links (a link to a route the platform does not register is left out).
|
||||
- Platform folder `platform/` (`PLATFORM_PATH`; outside git, optional): `config.php` replaces the platform
|
||||
config, `theme.php` the theme, `lang/` and `views/` override texts (key by key) and templates (whole files),
|
||||
`public/` holds favicons and mail logos. Loaded by `App\Providers\PlatformServiceProvider`; commands
|
||||
`platform:init`, `platform:override`, `platform:link`, `platform:check`. Use `theme_email_logo_url()` /
|
||||
`theme_email_logo_file()` for the email logo. Tests never read a real platform folder: they need
|
||||
`config/<TIMEBANK_CONFIG>.php`, so keep it on a development machine even when `platform/config.php` exists.
|
||||
- `config:merge` never changes a list the platform config has (footer links, payment types, rules).
|
||||
|
||||
### Themes
|
||||
|
||||
- `TIMEBANK_THEME`: `timebank_cc` (default), `uuro`, `vegetable`, `yellow`, defined in `config/themes.php`.
|
||||
Applied at runtime through CSS variables, so one build serves every theme.
|
||||
- Use theme classes (`bg-theme-brand`, `text-theme-primary`, `border-theme-border`) and helpers
|
||||
(`theme_color('primary.500')`, `theme_font(...)`, `theme_logo()`, `theme_email_logo()`, `theme_favicon()`).
|
||||
- Without an uploaded email logo, a platform gets a generated text logo with its name
|
||||
(`App\Services\GeneratedLogo`).
|
||||
|
||||
### UI
|
||||
|
||||
- All new views follow `references/STYLE_GUIDE.md`; `resources/views/livewire/mailings/manage.blade.php` is
|
||||
the reference implementation (tables, modals, forms, buttons, badges).
|
||||
- Buttons: `<x-jetstream.button>`, `secondary-button`, `danger-button`, `light-button`.
|
||||
- Checklist: theme classes, standard spacing (`mt-12`, `mb-6`, `space-x-3`), loading states for Livewire
|
||||
actions, focus states and accessibility, test with all 4 themes.
|
||||
|
||||
## Known pitfalls
|
||||
|
||||
- **Union count**: never `->count()` on a union of Eloquent builders (PDO bindings misalign). Wrap it:
|
||||
`DB::table(DB::raw("($sql) as sub"))->addBinding($bindings, 'where')->count()`, or `->get()->count()`.
|
||||
- **Large data**: stream or chunk (log files, backups, exports). Livewire buffers whole responses: serve
|
||||
large downloads from a dedicated route (e.g. `posts.backup-download`), not from a Livewire method.
|
||||
- **Dropdowns**: in Livewire + Alpine dropdowns use `@mousedown.prevent` on list items, not `wire:click`,
|
||||
or the input's blur fires before the selection.
|
||||
- **Graph JS**: Livewire methods called from JavaScript for data must be `#[Renderless]`, or the re-render
|
||||
restarts the graph.
|
||||
- **Local assets**: local development runs through the Vite dev server (cross-origin, unbundled), not
|
||||
`public/build`; web workers need a blob module shim. Check both paths.
|
||||
- **Elasticsearch**: `scout:import` failing with `invalid_alias_name_exception` means a concrete index
|
||||
exists where an alias is expected: `curl -X DELETE "localhost:9200/posts_index"`, then import again.
|
||||
- **Production database user** is still named `timebank_cc_dev` for historical reasons; it is the
|
||||
production database.
|
||||
Reference in new issue
Block a user