10 KiB
10 KiB
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 -> ../filesandpublic/geo -> ../resources/geo. The build must never copy uploaded media or GPX data intopublic/.
Request flow
public/index.php→Controller::handle().Controllerreads the action parameteraand validates request values withsetReqVal(), manages the session and checks the CSRF token. Then it dispatches through amatch:dispatch()handles public actions.dispatchAdmin()handles actions that requireUser::CLEARANCE_ADMIN.
Livetrail(extendsFranzz\Objects\Main) is the application facade. It ownsProject,Media,UserandMap, and returns JSON throughself::getJsonResult($bSuccess, $sLangId, $asData, $asLangParams).- With no action,
getAppMainPage()rendersresources/masks/index.html. It injects the app config (projects, user, constants, CSRF token) and the Vite assets read frompublic/.vite/manifest.json.
Adding a backend action
- Add the public method to
Livetrail(or to the domain class it delegates to) and returngetJsonResult(...). - Add the action to the right
matchinController. - If the action changes state, add it to
Controller::MUTATING_ACTIONSso it requires POST + CSRF. If it writes the session, also add it toSESSION_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 inresources/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 withDb::getId(TABLE). - Use the
Dbhelpers (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_timeis UTC,*_time/*_ontimestamps are Site Time, and thetimezonecolumn holds Local Time.
Frontend
src/app.jscreates the shared singletons andprovides them:appConfig,api,lang,projects,user. Components get them throughinject, never through globals or imports.- Routing is hash-based (
#page-item-item, separatorconsts.hash_sep), handled inApp.vue. Routes are declared inaoRoutes. Lazy-load non-default pages withdefineAsyncComponent. - 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 anErrorcarryingdesc_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(callparent::__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 inController. 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.keyin snake_case (account.invalid_email,error.commit_db,unit.days). - Every key must exist in
en.json,es.jsonandfr.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.scsswith@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/elseifon 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.
- Semicolons, except after the
- Short one-statement
ifwithout braces is common:if($x) return;. - Comments:
//Capitalized commentwith 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.jsonsymlinksvendor/franzz/objectsto../objectsand autoloads that namespace from the local source path. It also installs the dev dependencies (php-cs-fixer).- Production uses
composer.json, which installsfranzz/objectsfrom its Git repository. - Commit and publish
objectschanges before updating or deploying a Livetrail version that relies on them.
Before committing
- Run
composer lintandnpm run lintmanually 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, readconfig/settings-sample.phpinstead. It declares the sameSettingsconstants 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.phpas empty constants. - Pushing to
masterdeploys 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.