# GUI build-version badge **Date:** 2026-06-03 **Status:** approved (design) **Area:** `VERSION`, `build.sh`, `Dockerfile`, `src/presa_mano/version.py`, `src/presa_mano/main.py`, `web/index.html`, `web/app.js` ## Problem `src/` and `web/` are baked into the Docker image (no bind-mount), so code changes only take effect after an image rebuild. There is no visible indicator of which build the running container serves, so a stale deployment is invisible — exactly the confusion behind "non vedo modifiche alla GUI". We want a version shown in the GUI header that increments on every build, plus the build time. ## Decisions (locked) - **Version string:** `V..` (e.g. `V1.0.1`), **patch auto-increments every build**; build time shown too. - **Injection:** a build wrapper script passes the version + build time as Docker build args. - **Placement:** a pill badge in the GUI header, build time in its tooltip. ## Components ### `VERSION` (tracked file, repo root `presa-mano/VERSION`) Plain text `major.minor.patch`, initialized to `1.0.0`. Single source of truth for the number; persists across builds via the file on disk. The build script rewrites it (left as an uncommitted working-tree edit — the script does NOT auto-commit). ### `build.sh` (repo root, executable) ``` 1. cd to script dir; ensure VERSION exists (else seed 1.0.0) 2. parse current major.minor.patch 3. bump: default patch++; arg `minor` → minor++/patch=0; `major` → major++/minor=patch=0 4. write new VERSION 5. BUILD_TIME = $(date -u +"%Y-%m-%d %H:%M UTC") 6. (cd compose dir) docker compose build --build-arg APP_VERSION= --build-arg BUILD_TIME="" presa-mano 7. docker compose up -d presa-mano 8. echo deployed version ``` The compose file lives one level up (`/opt/docker/robotserver/docker-compose.yml`); the script resolves that dir and runs compose there. Usage: `./build.sh [patch|minor|major]` (default patch). ### `Dockerfile` Add late (before `CMD`), so changing them doesn't bust the dependency layers: ``` ARG APP_VERSION=dev ARG BUILD_TIME=unknown ENV APP_VERSION=${APP_VERSION} ENV BUILD_TIME=${BUILD_TIME} ``` Also `COPY VERSION ./` (cheap, makes the file fallback work inside the container too). ### `src/presa_mano/version.py` ```python def build_info() -> dict: """{'version', 'build_time'}. Prefer env (set at Docker build); fall back to the VERSION file + '(local)' for `uv run` dev.""" ``` - `version` = `APP_VERSION` env if set, else ` (local)` (or `0.0.0 (local)` if the file is unreadable). - `build_time` = `BUILD_TIME` env if set, else `(local)`. - `VERSION` file resolved as `Path(__file__).resolve().parents[2] / "VERSION"`. ### `src/presa_mano/main.py` ```python from .version import build_info @app.get("/api/version", tags=["meta"]) def version() -> dict: return build_info() ``` ### `web/index.html` In the header, next to `#activeRobotPill`, add: ```html V— ``` (reuses the existing pill styling). ### `web/app.js` Add and call once at startup (alongside `refreshHomeRobotSelector()`): ```javascript async function refreshAppVersion() { try { const r = await api('GET', '/api/version'); const pill = $('appVersionPill'); if (pill) { pill.textContent = `V${r.version}`; pill.title = `build ${r.build_time}`; } } catch {} } refreshAppVersion(); ``` ## Testing - Unit test `tests/test_version.py` for `build_info()`: - env set (`APP_VERSION`, `BUILD_TIME` via monkeypatch) → returns exactly those. - env unset (monkeypatch.delenv) → `version` ends with `(local)` and contains the `VERSION` file content; `build_time == "(local)"`. - Frontend: manual — after `./build.sh` the badge increments and the tooltip shows the new build time. ## Out of scope (YAGNI) - git SHA / dirty flag (user chose version+time only). - Auto-commit of the VERSION bump. - Frontend-vs-backend version mismatch warning.