This commit is contained in:
1 parent
518e74dfb8
commit
b0d8a79cdc
2 files changed
+169
-25
No files matched your search
@@ -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.
|
||||
Reference in new issue
Block a user