# 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 `