From 5054c46385d4ec5dd8936f01a944a8b3550b5880 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 10 May 2026 22:44:38 -0400 Subject: [PATCH 1/4] fix(web): collapse the entire Mobile app section when no APK bundled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When /api/client/version returns 404 (no APK in /app/client/, e.g. v2026.05.10.0 which had a failed CI APK-attach step), the previous shape rendered the section heading + intro paragraph with an empty hole where the button should be. Confusing — looks like a broken button. Moved the section wrapper, heading, and intro paragraph INSIDE MobileAppDownload so the whole block collapses together when info is null. Settings page just mounts ; nothing visible if no APK is available. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../lib/components/MobileAppDownload.svelte | 30 +++++++++++-------- web/src/routes/settings/+page.svelte | 8 +---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/web/src/lib/components/MobileAppDownload.svelte b/web/src/lib/components/MobileAppDownload.svelte index 67734517..cedaddff 100644 --- a/web/src/lib/components/MobileAppDownload.svelte +++ b/web/src/lib/components/MobileAppDownload.svelte @@ -36,17 +36,23 @@ {#if info} - - - - Get the Android app - - {info.version}{info.sizeBytes > 0 ? ` · ${formatSize(info.sizeBytes)}` : ''} +
+

Mobile app

+

+ Install the Android app paired to this server. Sign in with the same account. +

+
+ + + Get the Android app + + {info.version}{info.sizeBytes > 0 ? ` · ${formatSize(info.sizeBytes)}` : ''} + - - + +
{/if} diff --git a/web/src/routes/settings/+page.svelte b/web/src/routes/settings/+page.svelte index 60ec8465..2e71cee0 100644 --- a/web/src/routes/settings/+page.svelte +++ b/web/src/routes/settings/+page.svelte @@ -325,11 +325,5 @@ -
-

Mobile app

-

- Install the Android app paired to this server. Sign in with the same account. -

- -
+ From e16bdd95462e1e89f591b4e134125e37b6cdb410 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 10 May 2026 23:10:22 -0400 Subject: [PATCH 2/4] fix(web): read snake_case keys in MobileAppDownload (#397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server's clientVersionResponse marshals to {version, apk_url, size_bytes} but the component was reading body.apkUrl / body.sizeBytes (camelCase). The !body.apkUrl guard was always true → early return → download button never rendered even when /api/client/version returned 200 with valid data. Verified directly: /api/client/version returns {"version":"v2026.05.10.1","apk_url":"/api/client/apk","size_bytes":65301242} on the deployed v2026.05.10.1 image; the UI just wasn't reading those keys. Map wire (snake_case) → component (camelCase) explicitly via a WireInfo type so the boundary is auditable. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/src/lib/components/MobileAppDownload.svelte | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/web/src/lib/components/MobileAppDownload.svelte b/web/src/lib/components/MobileAppDownload.svelte index cedaddff..6766e96d 100644 --- a/web/src/lib/components/MobileAppDownload.svelte +++ b/web/src/lib/components/MobileAppDownload.svelte @@ -11,6 +11,10 @@ // bandwidth abuse on the APK stream, so this component only shows // for logged-in users. Mount in Settings, never in pre-auth surfaces. + // Server returns snake_case (clientVersionResponse in + // internal/api/client_assets.go). Map to a camelCase struct for + // the rest of the component. + type WireInfo = { version: string; apk_url: string; size_bytes: number }; type Info = { version: string; apkUrl: string; sizeBytes: number }; let info = $state(null); @@ -22,12 +26,12 @@ onMount(async () => { try { - const body = await api.get>('/api/client/version'); - if (!body.version || !body.apkUrl) return; + const body = await api.get>('/api/client/version'); + if (!body.version || !body.apk_url) return; info = { version: body.version, - apkUrl: body.apkUrl, - sizeBytes: body.sizeBytes ?? 0 + apkUrl: body.apk_url, + sizeBytes: body.size_bytes ?? 0 }; } catch { // 404 (no APK), 401 (somehow unauthed), network error — silent. From 4c4399c9bb647c1d322397ca654818a88e61ca23 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 10 May 2026 23:13:19 -0400 Subject: [PATCH 3/4] feat(server,web): expose server version via /healthz + display in Settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Came up debugging the in-app update flow — wasn't obvious which image the container was actually running without exec'ing in. New flow: ### Server - internal/server/version.go: new `var ServerVersion = "dev"`, overridden via -ldflags at build time. - /healthz response gains a "version" key alongside the existing "status" + "min_client_version". Backward-compat: existing clients ignore unknown JSON fields. Endpoint stays unauthenticated. ### Build - Dockerfile: new `ARG MINSTREL_VERSION=dev`, threaded into the go build -ldflags so the binary's ServerVersion is stamped at link time. Default "dev" preserves local `docker build` ergonomics. - .forgejo/workflows/release.yml: tags step also emits a `version` output (the git tag for tag pushes, "main" for branch pushes); build step passes it as `--build-arg MINSTREL_VERSION=...`. ### Web - web/src/lib/components/ServerVersion.svelte: small understated text ("Server v2026.05.10.2") that fetches /healthz on mount. Renders nothing on parse failure or pre-version images. - Mounted at the bottom of Settings. Co-Authored-By: Claude Opus 4.7 (1M context) --- .forgejo/workflows/release.yml | 7 +++- Dockerfile | 8 ++++- internal/server/server.go | 1 + internal/server/version.go | 10 ++++++ web/src/lib/components/ServerVersion.svelte | 37 +++++++++++++++++++++ web/src/routes/settings/+page.svelte | 5 +++ 6 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 web/src/lib/components/ServerVersion.svelte diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index d5902ea0..0366f156 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -50,9 +50,11 @@ jobs: if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then VERSION="${GITHUB_REF#refs/tags/}" echo "args=-t ${IMAGE}:${VERSION} -t ${IMAGE}:latest" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "::notice::Release build: ${VERSION} + latest" else echo "args=-t ${IMAGE}:main" >> "$GITHUB_OUTPUT" + echo "version=main" >> "$GITHUB_OUTPUT" echo "::notice::Main-branch build: :main" fi @@ -93,4 +95,7 @@ jobs: - name: Build and push if: steps.guard.outputs.ready == 'true' - run: docker buildx build --push ${{ steps.tags.outputs.args }} . + run: | + docker buildx build \ + --build-arg MINSTREL_VERSION="${{ steps.tags.outputs.version }}" \ + --push ${{ steps.tags.outputs.args }} . diff --git a/Dockerfile b/Dockerfile index 34cc41ad..436df34c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,13 @@ COPY . . # Overwrite the committed placeholder with the freshly-built SPA assets. COPY --from=web /web/build ./web/build ENV CGO_ENABLED=0 -RUN go build -trimpath -ldflags="-s -w" -o /out/minstrel ./cmd/minstrel +# Version stamping: release.yml passes the git tag via MINSTREL_VERSION +# build-arg; local `docker build` falls back to "dev". Surfaced at +# /healthz for operator-side image-version verification. +ARG MINSTREL_VERSION=dev +RUN go build -trimpath \ + -ldflags="-s -w -X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerVersion=${MINSTREL_VERSION}'" \ + -o /out/minstrel ./cmd/minstrel FROM debian:bookworm-slim RUN apt-get update \ diff --git a/internal/server/server.go b/internal/server/server.go index db363e6e..699418ac 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -148,6 +148,7 @@ func (s *Server) handleHealthz(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(map[string]string{ "status": "ok", + "version": ServerVersion, "min_client_version": MinClientVersion, }) } diff --git a/internal/server/version.go b/internal/server/version.go index b97672d0..d9ffb254 100644 --- a/internal/server/version.go +++ b/internal/server/version.go @@ -4,3 +4,13 @@ package server // accepts. Bump it when a server change requires a paired client update; // older clients see version_too_old at /healthz and refuse to operate. const MinClientVersion = "0.1.0" + +// ServerVersion is the deployed server image's version tag. Defaults to +// "dev" for local builds; overridden at link time via: +// +// -ldflags="-X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerVersion=v2026.05.10.2'" +// +// release.yml passes the git tag through MINSTREL_VERSION build-arg → +// Dockerfile ldflag. Surfaced at /healthz so operators can verify which +// image their container is running without exec'ing into it. +var ServerVersion = "dev" diff --git a/web/src/lib/components/ServerVersion.svelte b/web/src/lib/components/ServerVersion.svelte new file mode 100644 index 00000000..2fc80eea --- /dev/null +++ b/web/src/lib/components/ServerVersion.svelte @@ -0,0 +1,37 @@ + + +{#if version} +

Server {version}

+{/if} diff --git a/web/src/routes/settings/+page.svelte b/web/src/routes/settings/+page.svelte index 2e71cee0..4dbabce3 100644 --- a/web/src/routes/settings/+page.svelte +++ b/web/src/routes/settings/+page.svelte @@ -18,6 +18,7 @@ import { errCode } from '$lib/api/errors'; import { pushToast } from '$lib/stores/toast.svelte'; import MobileAppDownload from '$lib/components/MobileAppDownload.svelte'; + import ServerVersion from '$lib/components/ServerVersion.svelte'; const queryClient = useQueryClient(); @@ -326,4 +327,8 @@ + +
+ +
From c3ecd1bec8a001197c54b3ee2c9ccd14e6797cc0 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 10 May 2026 23:15:29 -0400 Subject: [PATCH 4/4] fix: gofmt -s on version.go (tab-indent doc-comment code block) --- internal/server/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/server/version.go b/internal/server/version.go index d9ffb254..1d87c7d4 100644 --- a/internal/server/version.go +++ b/internal/server/version.go @@ -8,7 +8,7 @@ const MinClientVersion = "0.1.0" // ServerVersion is the deployed server image's version tag. Defaults to // "dev" for local builds; overridden at link time via: // -// -ldflags="-X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerVersion=v2026.05.10.2'" +// -ldflags="-X 'git.fabledsword.com/bvandeusen/minstrel/internal/server.ServerVersion=v2026.05.10.2'" // // release.yml passes the git tag through MINSTREL_VERSION build-arg → // Dockerfile ldflag. Surfaced at /healthz so operators can verify which