diff --git a/client/src/VersionFooter.tsx b/client/src/VersionFooter.tsx new file mode 100644 index 0000000..594f576 --- /dev/null +++ b/client/src/VersionFooter.tsx @@ -0,0 +1,24 @@ +// #68: version/diagnostics display, shared by the two low-contrast +// footers (BarPicker, Stats) and Admin's fuller diagnostics block — +// same underlying info (build version + browser environment), different +// presentation for a quick glance vs. a bug-triage read. + +export const appVersion = __APP_VERSION__; + +export function VersionFooter() { + return ; +} + +// Read fresh on every call rather than once at module load — screen/ +// viewport size can change (rotation, window resize) between when the +// admin panel first mounts and when someone actually looks at this to +// triage a report. +export function diagLines(): string[] { + return [ + `Version: ${appVersion}`, + `User-Agent: ${navigator.userAgent}`, + `Bildschirm: ${screen.width}×${screen.height} @ ${window.devicePixelRatio}x`, + `Viewport: ${window.innerWidth}×${window.innerHeight}`, + `Sprache: ${navigator.language}`, + ]; +} diff --git a/client/src/admin/Admin.tsx b/client/src/admin/Admin.tsx index e3574bd..9dea9b9 100644 --- a/client/src/admin/Admin.tsx +++ b/client/src/admin/Admin.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'preact/hooks'; import type { Bar, Drink } from '@wutzcalc/shared'; import { errText, formatCents, j } from '../api'; +import { diagLines } from '../VersionFooter'; // `Drink`/`Bar` come from @wutzcalc/shared — the tablet code already did // this, but Admin.tsx used to re-declare its own near-identical copies, @@ -86,10 +87,25 @@ function Dashboard({ onLogout }: { onLogout: () => void }) { + ); } +// #68: browser/build metadata for triaging a device-specific bug report +// (e.g. "scrolling doesn't work on this one kiosk tablet") without having +// to walk someone through DevTools over the phone. +function Diagnose() { + return ( + <> +

Diagnose

+
    + {diagLines().map(l =>
  • {l}
  • )} +
+ + ); +} + function Exports() { return ( diff --git a/client/src/stats/Stats.tsx b/client/src/stats/Stats.tsx index 3adb6e8..d9e7483 100644 --- a/client/src/stats/Stats.tsx +++ b/client/src/stats/Stats.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from 'preact/hooks'; import { errText, formatCents, j } from '../api'; import { DayChart } from './DayChart'; +import { VersionFooter } from '../VersionFooter'; interface Totals { bar_id: number; bar_name: string; tx_count: number; paid_cents: number; crew_count: number; pfand_returns: number } interface PerDrink { drink_id: number; drink_name: string; sold_qty: number } @@ -257,6 +258,8 @@ function Dashboard({ data, isAdmin, onReset }: { data: StatsData; isAdmin: boole ))} + + ); } diff --git a/client/src/styles.css b/client/src/styles.css index f8a64f8..2565ac5 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -84,6 +84,21 @@ button.danger { background: #5a2a2a; border-color: #7a3a3a; color: #fff; } padding: 10px 18px; } +/* #68: version/build indicator for bug triage. margin-top: auto pins it + to the bottom of a flex column (.bar-picker) without disturbing that + column's own justify-content: center for everything above it; in a + plain block container (Stats.tsx's .admin) margin-top: auto has no + special effect, so it just falls in as the last item on the page — + same class works in both contexts without a variant. */ +.version-footer { + margin-top: auto; + text-align: center; + font-size: 12px; + font-weight: 600; + opacity: 0.35; + padding-top: 8px; +} + .topbar { display: flex; justify-content: space-between; @@ -311,6 +326,20 @@ button.danger { background: #5a2a2a; border-color: #7a3a3a; color: #fff; } } .admin .row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; flex-wrap: wrap; } .admin .muted { opacity: 0.6; } +/* #68: readable-but-secondary — not as dim as .muted, this is meant to + actually be read for bug triage, just not competing with the primary + admin content above it. break-all because navigator.userAgent runs + long and would otherwise overflow .admin's max-width. */ +.diag-list { + list-style: none; + margin: 0; + padding: 0; + font-size: 13px; + font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace; + opacity: 0.8; + word-break: break-all; +} +.diag-list li { padding: 3px 0; } .admin .login { max-width: 320px; margin: 80px auto; display: flex; flex-direction: column; gap: 12px; } .admin .chart { background: var(--surface); diff --git a/client/src/tablet/BarPicker.tsx b/client/src/tablet/BarPicker.tsx index fd81400..d2d4c83 100644 --- a/client/src/tablet/BarPicker.tsx +++ b/client/src/tablet/BarPicker.tsx @@ -1,6 +1,7 @@ import { useState } from 'preact/hooks'; import type { Bar } from '@wutzcalc/shared'; import { getTheme, setTheme } from '../api'; +import { VersionFooter } from '../VersionFooter'; interface Props { bars: Bar[] | null; @@ -35,6 +36,7 @@ export function BarPicker({ bars, onPick }: Props) { + ); } diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts new file mode 100644 index 0000000..57c1af3 --- /dev/null +++ b/client/src/vite-env.d.ts @@ -0,0 +1,5 @@ +/// + +// Injected by vite.config.ts's `define` — the short git commit sha the +// bundle was built from, or 'dev' if `.git` wasn't available at build time. +declare const __APP_VERSION__: string; diff --git a/client/vite.config.ts b/client/vite.config.ts index 8590f10..c8e32c3 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from 'vite'; import preact from '@preact/preset-vite'; import legacy from '@vitejs/plugin-legacy'; import { resolve } from 'node:path'; +import { execSync } from 'node:child_process'; // The dev-server proxy target — same default (3000) as the server's own // `process.env.PORT ?? 3000` in server/src/index.ts. Override with @@ -10,7 +11,23 @@ import { resolve } from 'node:path'; // WUTZ_SERVER_PORT=4000 pnpm dev:client const serverTarget = `http://localhost:${process.env.WUTZ_SERVER_PORT ?? 3000}`; +// #68: baked into the bundle at build time so the client can show "which +// build is this" for bug triage without a server round-trip. Deploys run +// `pnpm build` from a git checkout (see deploy/wutzcalc.service), but this +// stays defensive against a tarball/CI-artifact deploy with no `.git` — +// falls back to 'dev' rather than failing the build. +function appVersion(): string { + try { + return execSync('git rev-parse --short HEAD', { cwd: __dirname }).toString().trim(); + } catch { + return 'dev'; + } +} + export default defineConfig({ + define: { + __APP_VERSION__: JSON.stringify(appVersion()), + }, plugins: [ preact(), legacy({