diff --git a/frontend/src/App.css b/frontend/src/App.css
index 520b1cc..720eea7 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -159,6 +159,12 @@ body {
/* ── App shell ────────────────────────────────────────────────────────────── */
+.app {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
.app-header {
display: flex;
align-items: center;
@@ -191,11 +197,41 @@ body {
}
.app-main {
+ flex: 1;
max-width: 72rem;
margin: 1.5rem auto;
padding: 0 1.5rem;
}
+/* ── App footer ───────────────────────────────────────────────────────────── */
+
+.app-footer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 1rem 1.5rem;
+ font-size: 0.6875rem;
+ color: var(--color-text-muted);
+ border-top: 1px solid var(--color-border);
+}
+
+.app-footer__sep {
+ opacity: 0.4;
+}
+
+.app-footer__commit,
+.app-footer__repo {
+ color: var(--color-text-muted);
+ text-decoration: none;
+ transition: color 0.15s;
+}
+
+a.app-footer__commit:hover,
+a.app-footer__repo:hover {
+ color: var(--color-accent);
+}
+
/* ── Queue header ─────────────────────────────────────────────────────────── */
.queue-header {
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 7cf0c8a..ca81290 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -10,6 +10,7 @@ import MomentDetail from "./pages/MomentDetail";
import AdminReports from "./pages/AdminReports";
import AdminPipeline from "./pages/AdminPipeline";
import AdminDropdown from "./components/AdminDropdown";
+import AppFooter from "./components/AppFooter";
export default function App() {
return (
@@ -50,6 +51,8 @@ export default function App() {
} />
+
+
);
}
diff --git a/frontend/src/components/AppFooter.tsx b/frontend/src/components/AppFooter.tsx
new file mode 100644
index 0000000..98752be
--- /dev/null
+++ b/frontend/src/components/AppFooter.tsx
@@ -0,0 +1,47 @@
+const REPO_URL = "https://github.com/xpltdco/chrysopedia";
+
+export default function AppFooter() {
+ const commitUrl =
+ __GIT_COMMIT__ !== "dev"
+ ? `${REPO_URL}/commit/${__GIT_COMMIT__}`
+ : undefined;
+
+ return (
+
+ );
+}
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
index 11f02fe..b77c6f5 100644
--- a/frontend/src/vite-env.d.ts
+++ b/frontend/src/vite-env.d.ts
@@ -1 +1,5 @@
///
+
+declare const __APP_VERSION__: string;
+declare const __BUILD_DATE__: string;
+declare const __GIT_COMMIT__: string;
diff --git a/frontend/tsconfig.app.tsbuildinfo b/frontend/tsconfig.app.tsbuildinfo
index c9f1734..f4a3120 100644
--- a/frontend/tsconfig.app.tsbuildinfo
+++ b/frontend/tsconfig.app.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/ModeToggle.tsx","./src/components/ReportIssueModal.tsx","./src/components/StatusBadge.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/MomentDetail.tsx","./src/pages/ReviewQueue.tsx","./src/pages/SearchResults.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx"],"version":"5.6.3"}
\ No newline at end of file
+{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/ModeToggle.tsx","./src/components/ReportIssueModal.tsx","./src/components/StatusBadge.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/MomentDetail.tsx","./src/pages/ReviewQueue.tsx","./src/pages/SearchResults.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx"],"version":"5.6.3"}
\ No newline at end of file
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index c72472f..78070d1 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,8 +1,40 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
+import { execSync } from "child_process";
+import { readFileSync } from "fs";
+import { resolve } from "path";
+
+function getGitCommit(): string {
+ // In Docker builds, VITE_GIT_COMMIT is set via ENV from the build ARG
+ if (process.env.VITE_GIT_COMMIT) {
+ return process.env.VITE_GIT_COMMIT;
+ }
+ // Local dev: try git
+ try {
+ return execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
+ } catch {
+ return "dev";
+ }
+}
+
+function getAppVersion(): string {
+ try {
+ const pkg = JSON.parse(
+ readFileSync(resolve(__dirname, "package.json"), "utf-8"),
+ );
+ return pkg.version ?? "0.0.0";
+ } catch {
+ return "0.0.0";
+ }
+}
export default defineConfig({
plugins: [react()],
+ define: {
+ __APP_VERSION__: JSON.stringify(getAppVersion()),
+ __BUILD_DATE__: JSON.stringify(new Date().toISOString()),
+ __GIT_COMMIT__: JSON.stringify(getGitCommit()),
+ },
server: {
proxy: {
"/api": {