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

3.9 KiB

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

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

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:

<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()):

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.