feat(standalone): bottom-right version + source badge

A small fixed bottom-right overlay (VersionBadge) shown app-wide — on the home
page AND inside the loaded editor — surfacing this build's release tag and a
link to the source. The tag links to the exact commit when known (the pcbjam
commit pins the kicad + wxwidgets submodule revisions → our GPLv3
corresponding-source pointer, mirroring site Footer's BUILD_SHA), else the tag's
release page, else the repo root.

build-demo.mjs injects VITE_APP_TAG / VITE_GIT_SHA / VITE_REPO_URL (new --repo
flag, defaults to github.com/emergence-engineering/pcbjam).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergő Törcsvári 2026-06-20 08:01:50 +02:00
commit 0f53984407
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
5 changed files with 112 additions and 7 deletions

View file

@ -22,20 +22,39 @@ import {
import { join, resolve } from "node:path";
function parseArgs(argv) {
const a = { tag: null, cdn: "https://cdn.pcbjam.com" };
const a = {
tag: null,
cdn: "https://cdn.pcbjam.com",
repo: "https://github.com/emergence-engineering/pcbjam",
};
for (let i = 2; i < argv.length; i++) {
const next = () => argv[++i];
switch (argv[i]) {
case "--tag": a.tag = next(); break;
case "--cdn": a.cdn = next(); break;
case "--repo": a.repo = next(); break;
default: throw new Error(`unknown arg: ${argv[i]}`);
}
}
if (!a.tag) throw new Error("--tag <release tag> is required");
a.cdn = a.cdn.replace(/\/+$/, "");
a.repo = a.repo.replace(/\/+$/, "");
return a;
}
// Best-effort source commit for the version badge's corresponding-source link;
// empty string if git isn't available (CI shallow checkout etc.) — the badge
// then falls back to the tag's release page.
function gitSha(cwd) {
try {
return execFileSync("git", ["rev-parse", "HEAD"], { cwd })
.toString()
.trim();
} catch {
return "";
}
}
function main() {
const a = parseArgs(process.argv);
const repoRoot = resolve(process.cwd());
@ -55,12 +74,18 @@ function main() {
// Built-in offline symbols (no backend); cross-tab collab only.
VITE_LIBS_SOURCE: "static",
VITE_YJS_PROVIDER: "broadcastchannel",
// Build identity for the version badge. The commit is the GPLv3
// corresponding-source pointer (pins the kicad + wxwidgets submodules).
VITE_APP_TAG: a.tag,
VITE_GIT_SHA: gitSha(repoRoot),
VITE_REPO_URL: a.repo,
};
console.log(`build-demo: tag=${a.tag} cdn=${a.cdn}`);
console.log(` VITE_WASM_ROOT=${env.VITE_WASM_ROOT}`);
console.log(` VITE_WASM_MANIFEST=${env.VITE_WASM_MANIFEST}`);
console.log(` VITE_PROJECT_MANIFEST_URL=${env.VITE_PROJECT_MANIFEST_URL}`);
console.log(` VITE_APP_TAG=${env.VITE_APP_TAG} VITE_GIT_SHA=${env.VITE_GIT_SHA || "(none)"}`);
// Keep the dev-only WASM symlink out of the bundle (it'd copy 100s of MB into
// dist/; the CDN serves it). In CI it isn't present, so this is a no-op there.

View file

@ -1,4 +1,5 @@
import { Route, Routes } from "react-router-dom";
import { VersionBadge } from "@/components/VersionBadge";
import { HomePage } from "@/pages/HomePage";
import { LibToolPage } from "@/pages/LibToolPage";
import { ProjectView } from "@/pages/ProjectView";
@ -6,11 +7,15 @@ import { ToolPage } from "@/pages/ToolPage";
export default function App() {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/p/:project" element={<ProjectView />} />
<Route path="/p/:project/:tool/*" element={<ToolPage />} />
<Route path="/l/:lib/:tool" element={<LibToolPage />} />
</Routes>
<>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/p/:project" element={<ProjectView />} />
<Route path="/p/:project/:tool/*" element={<ToolPage />} />
<Route path="/l/:lib/:tool" element={<LibToolPage />} />
</Routes>
{/* Version + source link, bottom-right on every route (home + editor). */}
<VersionBadge />
</>
);
}

View file

@ -0,0 +1,49 @@
import { Github } from "lucide-react";
import { APP_GIT_SHA, APP_TAG, REPO_URL } from "@/lib/config";
/**
* Small bottom-right overlay showing this build's version + a link to the
* source. The standalone is GPLv3, so the tag links to the exact commit
* (corresponding-source pointer the repo commit pins the kicad + wxwidgets
* submodule revisions) when known, else the tag's release page, else the repo
* root. Mounted app-wide (App.tsx) so it shows on the home page AND inside the
* loaded editor. `fixed` keeps it viewport-anchored on every route; z-20 sits
* under the editor's boot overlay (z-30) so it's hidden until the tool is up.
*/
export function VersionBadge() {
const tag = APP_TAG ?? "dev";
const versionUrl = APP_GIT_SHA
? `${REPO_URL}/commit/${APP_GIT_SHA}`
: APP_TAG
? `${REPO_URL}/releases/tag/${APP_TAG}`
: REPO_URL;
const versionTitle = APP_GIT_SHA
? `commit ${APP_GIT_SHA.slice(0, 12)} — GPL corresponding source`
: APP_TAG
? `release ${APP_TAG}`
: "source repository";
return (
<div className="fixed bottom-3 right-3 z-20 flex items-center gap-2 rounded bg-black/70 px-2.5 py-1 font-mono text-[11px] text-white/80 shadow">
<a
href={versionUrl}
target="_blank"
rel="noreferrer"
title={versionTitle}
className="hover:text-white"
>
pcbjam {tag}
</a>
<span className="text-white/30">·</span>
<a
href={REPO_URL}
target="_blank"
rel="noreferrer"
title="Source on GitHub"
className="inline-flex items-center gap-1 hover:text-white"
>
<Github size={12} /> source
</a>
</div>
);
}

View file

@ -24,6 +24,26 @@ export const WASM_MANIFEST_FILE = import.meta.env.VITE_WASM_MANIFEST || null;
/** @deprecated Use WASM_ROOT + resolveWasmBase(). Kept for back-compat. */
export const WASM_ASSET_BASE_URL = WASM_ROOT;
// --- build identity (version badge + GPLv3 corresponding-source pointer) -------
// The standalone is GPLv3; the badge surfaces the build's tag + a link to the
// exact source. The repo commit pins the kicad + wxwidgets submodule revisions,
// so APP_GIT_SHA → github.com/.../commit/<sha> is our corresponding-source
// pointer (mirrors site/src/components/Footer.astro's BUILD_SHA). All three are
// injected at build time by scripts/deploy/build-demo.mjs; unset in a plain dev
// checkout (badge then shows "dev" → repo root).
/** Release tag for this build (e.g. "2.7.7"); shown in the version badge. */
export const APP_TAG = import.meta.env.VITE_APP_TAG || null;
/** Source commit this build was made from; the badge links to it as the GPLv3
* corresponding-source pointer. */
export const APP_GIT_SHA = import.meta.env.VITE_GIT_SHA || null;
/** Public source repository for the GPL editor (no trailing slash). */
export const REPO_URL = (
import.meta.env.VITE_REPO_URL || "https://github.com/emergence-engineering/pcbjam"
).replace(/\/+$/, "");
/**
* Where the standalone reads PROJECTS from (env VITE_PROJECT_SOURCE):
* "remote" (default) the @pcbjam/shared REST backend at API_BASE_URL.

View file

@ -6,6 +6,12 @@ interface ImportMetaEnv {
readonly VITE_WASM_ROOT?: string;
/** Per-release WASM manifest file under VITE_WASM_ROOT (e.g. "manifest-2.7.7.json"); enables versioned per-tool folders. */
readonly VITE_WASM_MANIFEST?: string;
/** Release tag for this build (e.g. "2.7.7"); shown in the version badge. */
readonly VITE_APP_TAG?: string;
/** Source commit this build was made from; the badge links to it as the GPLv3 corresponding-source pointer. */
readonly VITE_GIT_SHA?: string;
/** Public source repo URL for the GPL editor (default github.com/emergence-engineering/pcbjam). */
readonly VITE_REPO_URL?: string;
/** @deprecated legacy alias for VITE_WASM_ROOT. */
readonly VITE_WASM_ASSET_BASE_URL?: string;
/** Project source: "remote" (default, REST backend) | "static" (read-only CDN gallery, no backend). */