Dev guidelines for agents
Deploy Livetrail / deploy (push) Successful in 23s

This commit is contained in:
franzz committed 2026-10-23 14:17:21 +02:00
1 parent 518e74dfb8
commit b0d8a79cdc
2 files changed
+169 -25

No files matched your search

+169
View File
@@ -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/` | `<codename>.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_<singular>` 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 (`<appIcon>`, `<projectMapLink>`). 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 `<script setup>`.
### Translations
- Keys are nested `section.key` in snake_case (`account.invalid_email`, `error.commit_db`, `unit.days`).
- Every key must exist in `en.json`, `es.json` and `fr.json`.
### SCSS
- Partials are prefixed with `_` and named by scope with dots: `_page.project.panel.feed.scss`, `_color.scss`, `_var.scss`.
- Register them in `livetrail.scss` with `@use`.
- Use the shared variables (`var.$elem-spacing`, `var.$block-radius`, `color.$post`…) instead of literal values.
- Use kebab-case for CSS ids/classes and SCSS variables (`#feed-panel`, `.post-item`, `$trans-quick`).
## Code style
Both linters codify existing style. Their configs explain each rule.
- **Tabs** for indentation, LF line endings.
- **Single quotes** in PHP and JS.
- **No space** before control-structure parentheses: `if(...)`, `foreach(...)`, `switch(...)`, `function(x)`.
- PHP:
- Opening brace on the same line for classes and functions.
- Short array syntax `[]`.
- No spaces around the `.` concatenation operator.
- `else`/`elseif` on a new line after `}`.
- JS:
- Semicolons, except after the `export default {...}` of a Vue SFC.
- `{a, b}` without padding for objects; `import { x } from 'y'` with padding.
- Short one-statement `if` without braces is common: `if($x) return;`.
- Comments: `//Capitalized comment` with no space after `//`. Keep them sparse and explain the *why*.
## Development workflow
### Commands
| Task | Command |
|---|---|
| Dev build (watch) | `npm run dev`: development assets into `public/assets/`, also runs ESLint (reports issues, never blocks the watcher) |
| Production build | `npm run prod`: hashed assets into `public/assets/`, aborts on any ESLint error |
| JS/Vue lint | `npm run lint` |
| PHP lint / fix | `composer lint` (php-cs-fixer dry run) / `composer lint-fix` (applies it). Needs the dev dependencies below. |
| Local dev with sibling `objects` repo | `COMPOSER=composer.dev.json composer update` |
### Working with the `objects` library
- `composer.dev.json` symlinks `vendor/franzz/objects` to `../objects` and autoloads that namespace from the local source path. It also installs the dev dependencies (php-cs-fixer).
- Production uses `composer.json`, which installs `franzz/objects` from its Git repository.
- Commit and publish `objects` changes **before** updating or deploying a Livetrail version that relies on them.
### Before committing
- Run `composer lint` and `npm run lint` manually and fix any issues.
- There is no automated test suite. Verify changes by running the app locally.
## Rules for agents
- Never edit generated or runtime content:
- `public/assets/`, `public/.vite/`, `vendor/`, `node_modules/`
- `files/`, `resources/geo/*.geojson`
- Never read, open, search or print `config/settings.php`: it contains credentials and API tokens. To learn which settings exist, read `config/settings-sample.php` instead. It declares the same `Settings` constants with empty values.
- Don't copy media or GPX data into `public/`. They are exposed through symlinks only.
- Don't commit secrets. New settings go into `config/settings-sample.php` as empty constants.
- Pushing to `master` deploys to production (`.gitea/workflows/deploy.yml`). Never push without explicit approval.
- Keep changes in the style of the surrounding code. Don't reformat unrelated lines.