✨ feat(server): /api/info aggregator + frontend version footer
Sprint 2 of autonomous trainer day 2026-05-05. Mistral-implemented through ICM workspace ship-info-aggregator (bootstrapped backend + BDD before hitting price limit at stage 02), Claude-completed for frontend + Playwright + verifier + PR. Backend: - GET /api/info aggregator returning version, commit_short, build_date, uptime_seconds, cache_enabled, healthz_status (single round trip) - Optional cache via existing cache service (X-Cache: HIT/MISS) - BDD scenario @critical covers happy path + version regex; cache scenario kept under @skip @bdd-deferred until BDD harness gains a cache-enabled mode Frontend: - AppFooterView (dumb) + AppFooter (smart wrapper, useFetch) following the HealthDashboard / HealthDashboardView pattern - layouts/default.vue auto-applied via NuxtLayout in app.vue - humaniseUptime helper in utils/ - Playwright tests use route.fulfill mocking (decoupled from dev-proxy infra), assert visible AND content (PR #32 lesson) Docs: - documentation/API.md /api/info entry with schema and rationale - ADR-0026 documents composite endpoint vs separate calls choice Verifier verdict (skill-driven, audit at stage 04): APPROVE_WITH_NITS. Nits: handleInfo is 51 lines (could split into builder + emitter); X-Cache: DISABLED could improve ops clarity. Out-of-scope follow-up: existing tests/e2e/health.spec.ts happy path hits the same dev-proxy infra issue as my footer happy path before mocking. Same fix (server: false + route.fulfill) would apply.
This commit is contained in:
45
frontend/components/AppFooterView.vue
Normal file
45
frontend/components/AppFooterView.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import { humaniseUptime } from '~/utils/uptime'
|
||||
|
||||
export interface AppInfo {
|
||||
version: string
|
||||
commit_short: string
|
||||
build_date: string
|
||||
uptime_seconds: number
|
||||
cache_enabled: boolean
|
||||
healthz_status: string
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
data: AppInfo | null | undefined
|
||||
pending: boolean
|
||||
error: { message: string } | null | undefined
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer data-testid="app-footer">
|
||||
<p v-if="pending" data-testid="app-footer-pending">v?</p>
|
||||
<p v-else-if="error" data-testid="app-footer-error">v? · info unavailable</p>
|
||||
<p v-else-if="data" data-testid="app-footer-info">
|
||||
<span data-testid="app-footer-version">v{{ data.version }}</span>
|
||||
<span> · commit </span>
|
||||
<span data-testid="app-footer-commit">{{ data.commit_short }}</span>
|
||||
<span> · uptime </span>
|
||||
<span data-testid="app-footer-uptime">{{ humaniseUptime(data.uptime_seconds) }}</span>
|
||||
</p>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
footer {
|
||||
border-top: 1px solid #ccc;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
}
|
||||
footer p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user