Files
Presa-Mano/docs/superpowers/specs/2026-06-03-gui-build-version-badge-design.md
2026-06-03 14:40:52 +02:00

96 lines
3.9 KiB
Markdown

# 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<major>.<minor>.<patch>` (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=<new> --build-arg BUILD_TIME="<ts>" 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 `<VERSION file contents> (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
<span class="active-robot-pill" id="appVersionPill" title="versione build">V—</span>
```
(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.