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.
|
||||||
@@ -35,31 +35,6 @@ Live Trail is a self-hosted web application for sharing journeys on an interacti
|
|||||||
7. Add a GPX file named <project_codename>.gpx to /resources/geo/
|
7. Add a GPX file named <project_codename>.gpx to /resources/geo/
|
||||||
8. Go to #admin and create a new project (with code name = <project_codename>) & feed
|
8. Go to #admin and create a new project (with code name = <project_codename>) & feed
|
||||||
|
|
||||||
## Web Root
|
|
||||||
|
|
||||||
The web server should serve `public/` as the application document root. PHP source, configuration, Composer dependencies, uploaded files, and GPX data stay outside the public tree; `public/index.php` is the front controller and Vite writes generated frontend assets to `public/assets/`.
|
|
||||||
|
|
||||||
Runtime data is exposed through symlinks only: `public/files -> ../files` and `public/geo -> ../resources/geo`. The build must not copy uploaded media or GPX data into `public/`.
|
|
||||||
|
|
||||||
## Local Development
|
|
||||||
|
|
||||||
`npm run dev` watches and builds development assets into `public/assets/`. `npm run prod` builds hashed production assets into `public/assets/`. PHP reads `public/.vite/manifest.json` and populates the CSS, module, and preload parts defined in `resources/masks/index.html` while rendering the request.
|
|
||||||
|
|
||||||
When developing Livetrail and the sibling `objects` library together, install dependencies through the local Composer manifest:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
COMPOSER=composer.dev.json composer update
|
|
||||||
```
|
|
||||||
|
|
||||||
This makes Composer link `vendor/franzz/objects` to `../objects` and autoload that namespace directly from the local source path. Production continues to use `composer.json`, which installs `franzz/objects` from its Git repository. Commit and publish `objects` changes before updating/deploying a Livetrail version that relies on them.
|
|
||||||
|
|
||||||
## Before Committing
|
|
||||||
|
|
||||||
Run manually before pushing:
|
|
||||||
|
|
||||||
* `composer lint` - checks PHP style (php-cs-fixer, dry-run); `composer lint-fix` applies it. Requires the dev dependencies (`COMPOSER=composer.dev.json composer update`, see Local Development above).
|
|
||||||
* `npm run lint` - checks JS/Vue style (ESLint) on demand. It also already runs automatically as part of `npm run dev` (reports issues but never blocks the watcher) and `npm run prod` (aborts the build if any lint error is found).
|
|
||||||
|
|
||||||
## To Do List
|
## To Do List
|
||||||
|
|
||||||
* Add mail frequency slider
|
* Add mail frequency slider
|
||||||
|
|||||||
Reference in new issue
Block a user