From b0d8a79cdc1d675f526006cdac4668d8ff64af2d Mon Sep 17 00:00:00 2001 From: franzz Date: Fri, 23 Oct 2026 14:17:21 +0200 Subject: [PATCH] Dev guidelines for agents --- AGENTS.md | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ readme.md | 25 -------- 2 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..95b5cd7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,169 @@ +# AGENTS.md + +Guidelines for AI coding agents working on Livetrail. See `readme.md` for installation and dependencies. + +## Project overview + +Self-hosted web app that shares journeys on a map: SPOT GPS positions, GPX routes, posts, photos/videos and a chronological feed. + +- **Backend:** PHP 8.5+, MariaDB/MySQL, namespace `Franzz\Livetrail` (PSR-4 → `lib/`). +- **Frontend:** Vue 3 (Options API), MapLibre GL, SCSS, built with Vite. +- **Shared library:** `franzz/objects` (`Franzz\Objects\*`: `Main`, `PhpObject`, `Db`, `Translator`, `ToolBox`, `Mask`…). It lives in a separate repo (`../objects`). Change generic behaviour there, not by duplicating it here. + +## Architecture + +### Directory layout + +| Path | Role | +|---|---| +| `public/` | Web root only. `index.php` is the single front controller. `assets/` is Vite output (generated, never edit). `files` and `geo` are symlinks created by the build. | +| `lib/` | PHP classes, one class per file, file name = class name. | +| `config/` | `settings.php` (git-ignored, copied from `settings-sample.php`) defines the global `Settings` class of constants. Web server samples. | +| `resources/lang/*.json` | Translations (`en`, `es`, `fr`). | +| `resources/masks/*.html` | Server-side HTML templates (main page, emails). | +| `resources/geo/` | `.gpx` source routes; `.geojson` files are generated from them. | +| `src/` | Frontend sources: `app.js` (entry), `App.vue` (hash router), `components/`, `scripts/` (plain JS classes), `styles/`, `images/`. | +| `cli/cron.sh` | Hourly `a=update_project` call that polls the SPOT feeds. | + +### Web root + +- The web server serves `public/` as the document root. +- PHP sources, configuration, Composer dependencies, uploaded files and GPX data stay outside `public/`. +- Runtime data is exposed through symlinks only: `public/files -> ../files` and `public/geo -> ../resources/geo`. The build must never copy uploaded media or GPX data into `public/`. + +### Request flow + +1. `public/index.php` → `Controller::handle()`. +2. `Controller` reads the action parameter `a` and validates request values with `setReqVal()`, manages the session and checks the CSRF token. Then it dispatches through a `match`: + - `dispatch()` handles public actions. + - `dispatchAdmin()` handles actions that require `User::CLEARANCE_ADMIN`. +3. `Livetrail` (extends `Franzz\Objects\Main`) is the application facade. It owns `Project`, `Media`, `User` and `Map`, and returns JSON through `self::getJsonResult($bSuccess, $sLangId, $asData, $asLangParams)`. +4. With no action, `getAppMainPage()` renders `resources/masks/index.html`. It injects the app config (projects, user, constants, CSRF token) and the Vite assets read from `public/.vite/manifest.json`. + +### Adding a backend action + +- Add the public method to `Livetrail` (or to the domain class it delegates to) and return `getJsonResult(...)`. +- Add the action to the right `match` in `Controller`. +- If the action changes state, add it to `Controller::MUTATING_ACTIONS` so it requires POST + CSRF. If it writes the session, also add it to `SESSION_WRITING_ACTIONS`. +- Read new request parameters through `setReqVal()`. Use a validation such as `'positiveInt'` where it applies. +- Results and errors carry a translation key (`desc_lang_id`), not literal text. Add that key to **all** files in `resources/lang/`. + +### Database + +- The schema is declared in code in `Livetrail::getSqlOptions()` (`tables`, `types`, `constraints`, `cascading_delete`). Update it there when adding columns or tables. +- Table names are class constants, e.g. `Project::PROJ_TABLE`, `Feed::MSG_TABLE`, `Media::MEDIA_TABLE`. Never hard-code table names outside raw SQL strings. +- Primary/foreign keys follow `id_` and are obtained with `Db::getId(TABLE)`. +- Use the `Db` helpers (`selectRows`, `selectColumn`, `insertRow`, `updateRow`, `deleteRow`) rather than raw SQL wherever possible. +- Timezones: follow the "Site Time" / "Local Time" convention documented at the top of `lib/Livetrail.php`. `unix_time` is UTC, `*_time`/`*_on` timestamps are Site Time, and the `timezone` column holds Local Time. + +### Frontend + +- `src/app.js` creates the shared singletons and `provide`s them: `appConfig`, `api`, `lang`, `projects`, `user`. Components get them through `inject`, never through globals or imports. +- Routing is hash-based (`#page-item-item`, separator `consts.hash_sep`), handled in `App.vue`. Routes are declared in `aoRoutes`. Lazy-load non-default pages with `defineAsyncComponent`. +- Talk to the server only through `api.get(sAction, params)` / `api.post(sAction, params)` (`src/scripts/api.js`). This adds the timezone and CSRF token and throws an `Error` carrying `desc_lang_*` on failure. +- Use translations through `lang.get('section.key', params)`. Placeholders are `$0`, `$1`… +- Use the import aliases `@components`, `@scripts`, `@styles`, `@images`. +- No `v-html` (lint error). + +## Naming conventions + +### Hungarian-style prefixes (PHP and JS) + +Variables, parameters and properties carry a type prefix: + +| Prefix | Type | Example | +|---|---|---| +| `s` | string | `$sCodeName`, `sAction` | +| `i` | integer / number | `$iProjectId`, `iDistance` | +| `f` / `d` | float / decimal | `$fValue`, `$dValue` | +| `b` | boolean | `$bSuccess`, `bSecure` | +| `o` | object / instance | `$oDb`, `oFeed` | +| `a` | generic array | `$aValues` | +| `as` | array of strings | `$asProject`, `asParams` | +| `ai`, `ao` | array of ints / objects | `$aiCenter`, `aoCoords` | +| `j` | JSON string | `jNewHash` | + +Exceptions: loop counters (`i`), short comparator args (`$a`, `$b`), Vue `data`/`props`/`computed` names (plain camelCase: `postMessage`, `anchorVisible`), and keys of JSON exchanged with the backend (snake_case). + +### PHP + +- Classes: PascalCase, one per file, extending `Franzz\Objects\PhpObject` (call `parent::__construct(__CLASS__)`). Report errors with `$this->addError()`. +- Methods: camelCase with verb prefixes: `get*`, `set*`, `is*`/`has*`, `add*`, `create*`, `delete*`, `check*`. +- Constants: `UPPER_SNAKE_CASE`, grouped under short comments (`//DB Tables`, `//Spot Mode`). Table constants end in `_TABLE`. +- Controller actions, DB columns, array keys sent to the frontend and request parameters: `snake_case` (`id_project`, `ref_type`, `last_update`). +- New code: add parameter/return types (`string $sCodeName`, `: int`) as in `Controller`. Don't mass-retype old code. + +### JavaScript / Vue + +- Component files: PascalCase, named after their parent area, for example: + - `App*` for generic UI (`AppIcon`, `AppButton`) + - `Project*` for the project page (`ProjectFeed`, `ProjectPost`) + - `Admin*` for the admin page +- Templates use camelCase component tags (``, ``). Import names are mixed between camelCase and PascalCase, so follow the file you are editing. +- Script modules in `src/scripts/`: lowerCamelCase file names, each exporting a class (`export default class Api`) or named functions (`common.js`). +- Module-level constants: `UPPER_SNAKE_CASE`. +- Components use the Options API (`data`, `inject`, `computed`, `methods`…), not `