# AGENTS.md This file provides guidance to AI agents when working with code in this repository. ## What this is A KDE Plasma 6 plasmoid (pure QML + JavaScript, no build step) showing real-time MARTA bus arrivals. The runtime code under `contents/` is what ships; Node is used only for tooling and tests. ## Commands All QML tooling (qmltestrunner, qmllint, qmlformat, kpackagetool6, actionlint) comes from the Nix dev shell; Node-only commands work without it: ```bash nix develop .#ci # enter the dev/CI shell npm test # Node tests (scripts/) npm run test:qml # QML tests (qmltestrunner, offscreen) npm run format:check # qmlformat diff check — fix with: qmlformat -i npm run lint:qml # qmllint on contents/ui QML npm run package # build dist/marta-bus-arrivals-.plasmoid + SHA256SUMS ``` Single test: - Node: `node --test --test-name-pattern "" tests/node/stop-names.test.mjs` - QML: `qmltestrunner -platform offscreen -input tests/qml/tst_arrivals.qml` (one file), or append `Arrivals::test_calculatesCountdownMinutes` to run one function Run the widget locally: `kpackagetool6 --type Plasma/Applet --upgrade "$PWD"` then `plasmawindowed io.github.alyraffauf.martabusarrivals`. Regenerate stop data: `node update-stop-names.mjs` (downloads MARTA's static GTFS; a monthly GitHub workflow does this and opens a PR — note that bot PRs created with `GITHUB_TOKEN` do not trigger the CI workflow, so that workflow runs the full test suite itself before opening the PR). ## Architecture Data flows one way: ``` FeedLoader.qml ──▶ lib/gtfs-realtime.js ──▶ main.qml state ──▶ lib/arrivals.js ──▶ ArrivalBoard ▶ StopSection ▶ ArrivalRow (XHR fetch) (protobuf parser) (owns everything) (group/format) ``` - `main.qml` owns all applet state (`arrivals`, `currentTime`, error/loaded flags) and all timing. Two timers: `refreshTimer` polls the feed, `countdownTimer` advances `currentTime` only at the moment a displayed ETA minute can change (`nextCountdownDelay()`). Both are gated on `boardVisible` (expanded in a panel; always on for desktop/Planar) — keep new periodic work behind that gate. `stopIdKey` exists so label-only config edits don't wipe loaded arrivals; only genuine stop-ID changes reset state. - `lib/gtfs-realtime.js` is a hand-rolled protobuf wire-format parser (QML's JS engine has no TextDecoder or protobuf support). It parses every field of every message generically, then extracts only trip_update route/stop/time fields. It must tolerate 10-byte varints — negative int32s (e.g. `StopTimeEvent.delay` for an early bus) are sign-extended to 10 bytes, and a throw anywhere aborts the whole feed. Arrival timestamps are epoch **seconds**; milliseconds appear only at display/countdown math. - `lib/arrivals.js` zips the parallel `stopIds`/`stopLabels` StringLists (index-aligned — every mutation site must keep them in sync) into stop objects, groups arrivals per stop, and computes minute countdowns. - `lib/stop-names.js` is **generated** (7k-entry lookup from MARTA static GTFS) — never edit by hand; change `scripts/stop-names.mjs` and regenerate. Its CSV parsing is header-driven (GTFS guarantees column names, not order) and must handle quoted embedded newlines. - `ConfigGeneral.qml` uses Plasma's `cfg_` property convention for load/dirty-tracking/apply — do not reintroduce manual `saveConfig()`/`configurationChanged` wiring. Config keys are declared in `contents/config/main.xml`. - Tests hand-encode protobuf messages (`tests/qml/tst_gtfs_realtime.qml`); when adding parser features, extend those encoders rather than checking in binary fixtures. ## Constraints (from CONTRIBUTING.md) - Keep UI, feed loading, parser, and generated-data concerns in their separate files. - Never add default stop IDs or other personal configuration to package defaults. - The MARTA rainbow mark (`MartaMark.qml`) is used with MARTA's authorization — don't alter or extend MARTA branding. ## Releasing Bump `KPlugin.Version` in `metadata.json`, add a matching `CHANGELOG.md` section, and push an annotated tag `vX.Y.Z` that matches the metadata version exactly (CI verifies via `scripts/verify-release-version.mjs`). See `docs/releasing.md`.