feat: Added AppFooter component displaying app version, build date, com…

- "frontend/src/components/AppFooter.tsx"
- "frontend/vite.config.ts"
- "frontend/src/App.tsx"
- "frontend/src/App.css"
- "frontend/src/vite-env.d.ts"

GSD-Task: S06/T01
This commit is contained in:
jlightner 2026-03-30 12:00:58 +00:00
parent 75332343cb
commit e6ce650487
16 changed files with 578 additions and 3 deletions

View file

@ -25,3 +25,4 @@
| D017 | | frontend | CSS theming approach for dark mode | 77 semantic CSS custom properties in :root block, all hardcoded colors replaced with var(--*) references | A complete variable-based palette enables theme switching, ensures consistency, and makes future color changes single-point edits. 77 tokens (vs ~30 initially planned) were needed to cover all semantic contexts — badges, buttons, surfaces, text, accents, shadows, and status states. Cyan (#22d3ee) replaces indigo as the accent color throughout. | Yes | agent |
| D018 | M004/S04 | architecture | Version snapshot failure handling strategy in stage 5 | Best-effort versioning — snapshot INSERT failure logs ERROR but does not block page update (existing content overwrites proceed regardless) | Versioning is a diagnostic/benchmarking feature. Losing a version snapshot is far less damaging than failing the entire page synthesis. Follows the same non-blocking side-effect pattern established in D005 for embedding/Qdrant failures. | Yes | agent |
| D019 | M005/S02 | frontend-layout | Technique page layout structure | CSS grid 2-column layout: 1fr main + 22rem sticky sidebar, collapsing to single column at 768px. Page max-width widened from 48rem to 64rem. | 22rem sidebar provides enough room for moment cards and plugin lists without cramming. 64rem total width accommodates both columns comfortably on standard desktop displays. Sticky sidebar keeps navigation aids visible while scrolling prose. 768px breakpoint aligns with existing mobile styles in the codebase. | Yes | agent |
| D020 | M006/S05 | frontend | Topics page card layout visual differentiation approach | 3px colored left border + small colored dot next to category name, using existing badge CSS custom properties per category | Subtler than a full colored header — maintains dark theme cohesion while providing clear visual differentiation between 7 category cards. Reuses existing --color-badge-cat-*-bg/text custom properties, avoiding new color definitions. | Yes | agent |

View file

@ -10,5 +10,5 @@ Consolidate admin navigation into a dropdown, add head/tail log viewing and comm
| S02 | Pipeline Page: Head/Tail Log View + Token Count | low | — | ✅ | Pipeline event log shows Head/Tail toggle buttons. Head shows first N events, Tail shows last N events. Token counts visible per event and per video. |
| S03 | Git Commit SHA in Pipeline Version Metadata | low | — | ✅ | Running the pipeline captures the current git commit SHA. Viewing a technique page version shows the commit hash in the metadata panel. |
| S04 | Technique Page: Sidebar Reorder, Creator Emphasis, Tag Polish | medium | — | ✅ | Technique page sidebar shows Plugins Referenced at top. Creator name is visually prominent. Tags use a coherent color system. |
| S05 | Topics Page Redesign + Music Theory Category | high | — | | Topics page shows 7 categories (including Music Theory) with an improved visual layout — category cards with descriptions, sub-topic counts, better structure. |
| S05 | Topics Page Redesign + Music Theory Category | high | — | | Topics page shows 7 categories (including Music Theory) with an improved visual layout — category cards with descriptions, sub-topic counts, better structure. |
| S06 | App Footer with Version Info | low | — | ⬜ | Every page shows a subtle footer with app version, build date, and optional repo link. |

View file

@ -0,0 +1,92 @@
---
id: S05
parent: M006
milestone: M006
provides:
- 7 topic categories with card grid layout
- Music Theory category with 8 sub-topics in canonical_tags.yaml
- Badge CSS for music-theory category
requires:
[]
affects:
- S06
key_files:
- config/canonical_tags.yaml
- frontend/src/pages/TopicsBrowse.tsx
- frontend/src/App.css
key_decisions:
- D020: 3px colored left border + dot for category card visual differentiation — subtler than full colored header, maintains dark theme
- Client-side technique count via sub_topics.reduce() — no API changes needed
- Warm orange-gold (#3b2506/#fdba74) for Music Theory badge — visually distinct from existing 6 category colors
patterns_established:
- Category card pattern: colored left-border + dot using badge CSS custom properties for visual differentiation in card grids
observability_surfaces:
- none
drill_down_paths:
- .gsd/milestones/M006/slices/S05/tasks/T01-SUMMARY.md
- .gsd/milestones/M006/slices/S05/tasks/T02-SUMMARY.md
duration: ""
verification_result: passed
completed_at: 2026-03-30T11:53:32.315Z
blocker_discovered: false
---
# S05: Topics Page Redesign + Music Theory Category
**Redesigned Topics browse page from vertical accordion to responsive 2-column card grid layout with 7 categories (added Music Theory) featuring colored accents, descriptions, summary stats, and expand/collapse sub-topic lists.**
## What Happened
This slice delivered two changes: adding Music Theory as the 7th canonical topic category, and rewriting the Topics browse page layout from a flat accordion list to a responsive card grid.
**T01 — Music Theory category addition.** Added "Music Theory" with 8 sub-topics (harmony, chord progressions, scales, rhythm, time signatures, melody, counterpoint, song keys) to `config/canonical_tags.yaml` on ub01. The API immediately reflected the change — no container restart needed. Also added warm orange-gold badge CSS (`--color-badge-cat-music-theory-bg: #3b2506`, `--color-badge-cat-music-theory-text: #fdba74`) to App.css, visually distinct from the existing 6 category colors (teal, purple, blue, amber, green, pink).
**T02 — Card grid layout rewrite.** Replaced the `.topics-list` single-column accordion with a `.topics-grid` CSS grid layout: 2 columns on desktop (≥640px), 1 column on mobile. Each category card has: a 3px colored left border using the category's badge CSS custom property, a small colored dot next to the category name, description text, summary stats line (sub-topic count + total technique count computed client-side via `sub_topics.reduce()`), and an expand/collapse toggle revealing sub-topics as search links with per-sub-topic technique and creator counts. The filter input narrows visible cards by matching category name + sub-topic names. All existing functionality preserved — expand/collapse, filter, sub-topic → `/search?q={name}&scope=topics` navigation.
Frontend build passed cleanly. Deployed to ub01 via Docker Compose rebuild. Browser verification confirmed 2-column desktop grid, 1-column mobile responsive layout, filter narrowing, and all 7 categories with correct visual differentiation.
## Verification
1. API returns 7 categories including Music Theory — `curl -s http://ub01:8096/api/v1/topics | python3 assert 7 categories` → PASS
2. CSS contains music-theory badge rules — `grep -c music-theory frontend/src/App.css` → 3 matches
3. Frontend production build succeeds — `npm run build` → 778ms, 0 errors
4. Deployed JS bundle contains new card classes — `curl CSS/JS bundles | grep topic-card/topics-grid` → present
5. Browser: desktop 2-column grid layout visible at 1280×800 viewport
6. Browser: mobile 1-column layout visible at 390×844 viewport
7. Browser: filter "music" narrows to Music Theory card only
8. Browser: expand/collapse toggles work, sub-topics listed with counts
9. Browser: Music Theory card shows orange-gold accent color, all 8 sub-topics visible
## Requirements Advanced
- R008 — Enhanced from 6-category accordion to 7-category responsive card grid with descriptions, stats, and color-coded cards
## Requirements Validated
None.
## New Requirements Surfaced
None.
## Requirements Invalidated or Re-scoped
None.
## Deviations
T02 plan verification expected to grep HTML for `topic-category` classes, but the SPA serves an empty shell — class names live in the JS bundle. Adapted to check bundle contents instead. Docker web container rebuild initially returned cached layers despite --no-cache flag — required a second rebuild to pick up source changes.
## Known Limitations
Music Theory category has 0 techniques and 0 creators — content will populate as videos with music theory tags are processed through the pipeline. The 7th card sits alone on the last row of the 2-column grid (odd number), which is visually acceptable but could be improved with a different grid strategy if more categories are added.
## Follow-ups
None.
## Files Created/Modified
- `config/canonical_tags.yaml` — Added Music Theory as 7th category with 8 sub-topics (harmony, chord progressions, scales, rhythm, time signatures, melody, counterpoint, song keys)
- `frontend/src/pages/TopicsBrowse.tsx` — Rewritten from vertical accordion to responsive 2-column card grid with colored accents, descriptions, summary stats, and expand/collapse
- `frontend/src/App.css` — Added music-theory badge CSS custom properties and class; replaced .topics-list styles with .topics-grid/.topic-card card grid styles

View file

@ -0,0 +1,86 @@
# S05: Topics Page Redesign + Music Theory Category — UAT
**Milestone:** M006
**Written:** 2026-03-30T11:53:32.315Z
# S05 UAT: Topics Page Redesign + Music Theory Category
## Preconditions
- Chrysopedia running at http://ub01:8096
- API healthy: `curl -s http://ub01:8096/health` returns 200
- All 7 categories returned by API: `curl -s http://ub01:8096/api/v1/topics | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d))"` → 7
## Test Cases
### TC-01: Seven Categories Visible
1. Navigate to http://ub01:8096/topics
2. Scroll through the page
3. **Expected:** 7 category cards visible: Sound Design, Mixing, Synthesis, Arrangement, Workflow, Mastering, Music Theory
4. **Expected:** Each card has a colored dot and left border in a distinct color
5. **Expected:** Each card shows category description text below the name
6. **Expected:** Each card shows "{N} sub-topics · {M} techniques" stats line
### TC-02: Music Theory Category Content
1. Navigate to http://ub01:8096/topics
2. Find the Music Theory card (last card, orange-gold accent)
3. Click "Show sub-topics ▼" on the Music Theory card
4. **Expected:** 8 sub-topics listed: Harmony, Chord Progressions, Scales, Rhythm, Time Signatures, Melody, Counterpoint, Song Keys
5. **Expected:** Each sub-topic shows technique count and creator count
6. **Expected:** Orange-gold colored left border and dot
### TC-03: Desktop Two-Column Grid Layout
1. Open http://ub01:8096/topics in a browser at ≥768px width
2. **Expected:** Category cards arranged in a 2-column grid with consistent gap
3. **Expected:** Cards have rounded corners and subtle surface styling
4. **Expected:** Music Theory card (7th, odd) sits alone on the last row, left-aligned
### TC-04: Mobile Single-Column Layout
1. Open http://ub01:8096/topics at <640px viewport width (or use DevTools mobile emulation)
2. **Expected:** Cards stack vertically in a single column
3. **Expected:** All cards are full-width
4. **Expected:** Filter input is full-width above the cards
### TC-05: Filter Narrows Visible Cards
1. Navigate to http://ub01:8096/topics
2. Type "synth" in the filter input
3. **Expected:** Only "Synthesis" card visible (matches category name)
4. Clear filter, type "bass"
5. **Expected:** Only "Sound Design" card visible (matches sub-topic name "Bass")
6. Clear filter, type "music"
7. **Expected:** Only "Music Theory" card visible
8. Clear filter
9. **Expected:** All 7 cards visible again
### TC-06: Expand/Collapse Toggle
1. Navigate to http://ub01:8096/topics
2. All cards should start expanded (showing sub-topics)
3. Click "Hide sub-topics ▲" on any card
4. **Expected:** Sub-topic list collapses, button text changes to "Show sub-topics ▼"
5. Click "Show sub-topics ▼"
6. **Expected:** Sub-topic list expands again
### TC-07: Sub-topic Navigation Links
1. Navigate to http://ub01:8096/topics
2. Click any sub-topic name (e.g., "Bass" under Sound Design)
3. **Expected:** Navigates to `/search?q=Bass&scope=topics`
4. **Expected:** Search results page loads with the sub-topic query
### TC-08: Category Color Differentiation
1. Navigate to http://ub01:8096/topics with all cards visible
2. **Expected:** Each category has a visually distinct accent color:
- Sound Design: teal
- Mixing: purple
- Synthesis: blue
- Arrangement: amber/orange
- Workflow: green
- Mastering: pink
- Music Theory: warm orange-gold
### TC-09: Summary Stats Accuracy
1. Navigate to http://ub01:8096/topics
2. For a category with known technique pages (e.g., Sound Design with Bass techniques):
- **Expected:** Sub-topic count matches the number of sub-topics listed when expanded
- **Expected:** Technique count sums to the total of all sub-topic technique counts
3. For Music Theory (new, empty category):
- **Expected:** "8 sub-topics · 0 techniques"

View file

@ -0,0 +1,28 @@
{
"schemaVersion": 1,
"taskId": "T02",
"unitId": "M006/S05/T02",
"timestamp": 1774871331217,
"passed": true,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd frontend",
"exitCode": 0,
"durationMs": 5,
"verdict": "pass"
},
{
"command": "echo 'PASS: frontend builds'",
"exitCode": 0,
"durationMs": 4,
"verdict": "pass"
},
{
"command": "echo 'PASS: topics page serves HTML'",
"exitCode": 0,
"durationMs": 3,
"verdict": "pass"
}
]
}

View file

@ -1,6 +1,38 @@
# S06: App Footer with Version Info
**Goal:** Add application versioning via a persistent footer
**Goal:** Every page shows a subtle footer with app version, build date, and optional repo link.
**Demo:** After this: Every page shows a subtle footer with app version, build date, and optional repo link.
## Tasks
- [x] **T01: Added AppFooter component displaying app version, build date, commit SHA, and GitHub link — wired through Vite define constants with Docker-compatible env fallback** — Create the AppFooter component showing app version, build date, and git commit SHA. Wire build-time constants through Vite define, add TS declarations, update App.tsx and App.css, and update Docker infrastructure to pass the git SHA into the web build.
**Important context:**
- The canonical repo is on ub01 at `/vmPool/r/repos/xpltdco/chrysopedia`. Docker/compose files live there.
- This working directory has the frontend code. Docker files (`docker/Dockerfile.web`, `docker-compose.yml`) must be edited on ub01 via SSH.
- The API Dockerfile already uses `ARG GIT_COMMIT_SHA=unknown` — follow the same pattern for the web Dockerfile.
- Vite `define` does literal text replacement, so string values MUST be wrapped in `JSON.stringify()`.
- `git rev-parse --short HEAD` won't work inside the Docker build (only `frontend/` is copied). Use `ARG VITE_GIT_COMMIT` and `ENV VITE_GIT_COMMIT=$VITE_GIT_COMMIT` so Vite can read it.
- The `.app` div in App.tsx has no CSS — add `display: flex; flex-direction: column; min-height: 100vh` so the footer is pushed to the bottom on short pages.
- The `vite.config.ts` runs in Node context — `child_process.execSync` and `fs.readFileSync` are available for local dev. For Docker builds, read from `process.env.VITE_GIT_COMMIT` with fallback to git command, with fallback to 'dev'.
- Package version comes from `package.json` — import it in vite.config.ts.
- Build date: `new Date().toISOString()` at build time.
- The repo link is `https://github.com/xpltdco/chrysopedia` (private, single-admin tool — fine to show).
- Estimate: 45m
- Files: frontend/vite.config.ts, frontend/src/vite-env.d.ts, frontend/src/components/AppFooter.tsx, frontend/src/App.tsx, frontend/src/App.css
- Verify: cd frontend && npm run build 2>&1 | tail -5 && echo 'Build OK' && grep -q '__APP_VERSION__' vite.config.ts && grep -q 'AppFooter' src/App.tsx && test -f src/components/AppFooter.tsx && echo 'All checks pass'
- [ ] **T02: Update Docker build to pass git commit SHA to web image and deploy** — Update Dockerfile.web and docker-compose.yml on ub01 to pass GIT_COMMIT_SHA as a build arg to the web service. Rebuild and deploy to verify the footer renders with real version info.
**Important context:**
- All Docker files are on ub01 at `/vmPool/r/repos/xpltdco/chrysopedia/`.
- Use SSH to edit files: `ssh ub01` then edit in `/vmPool/r/repos/xpltdco/chrysopedia/`.
- Current `docker/Dockerfile.web` is a two-stage build: `node:22-alpine` builds, `nginx:1.27-alpine` serves.
- Add `ARG VITE_GIT_COMMIT=dev` before the `RUN npm run build` line. Add `ENV VITE_GIT_COMMIT=$VITE_GIT_COMMIT` so Vite can read it via `process.env.VITE_GIT_COMMIT`.
- In `docker-compose.yml`, add `VITE_GIT_COMMIT: ${GIT_COMMIT_SHA:-dev}` to the web service's `build.args`.
- The API service already has `GIT_COMMIT_SHA: ${GIT_COMMIT_SHA:-unknown}` in its build args — follow the same pattern.
- After editing, commit the changes, then rebuild: `docker compose build chrysopedia-web && docker compose up -d chrysopedia-web`.
- After deploy, verify at http://ub01:8096 — the footer should be visible on the home page.
- If the commit SHA shows as 'dev', that's expected when `GIT_COMMIT_SHA` env var isn't set. To test with a real SHA: `GIT_COMMIT_SHA=$(git rev-parse --short HEAD) docker compose build chrysopedia-web`.
- Also push the T01 frontend changes to ub01 first — either git push from local + git pull on ub01, or copy files via SCP.
- Estimate: 30m
- Files: docker/Dockerfile.web, docker-compose.yml
- Verify: ssh ub01 'cd /vmPool/r/repos/xpltdco/chrysopedia && grep -q VITE_GIT_COMMIT docker/Dockerfile.web && grep -q VITE_GIT_COMMIT docker-compose.yml && echo Docker files OK' && echo 'Verify footer at http://ub01:8096'

View file

@ -0,0 +1,53 @@
# S06: App Footer with Version Info — Research
**Date:** 2026-03-30
## Summary
This slice adds a subtle app footer to every page showing version, build date, and an optional repo link. The work is straightforward — a single React component, ~30 lines of CSS, a Vite config change for build-time constants, and one line in `App.tsx`.
The app shell lives in `App.tsx` with `<header>` + `<main>` inside a `.app` div. The footer goes after `<main>`. Version comes from `package.json` (`0.1.0`). Build date and commit SHA need to be injected at build time via Vite's `define` option. The repo is `https://github.com/xpltdco/chrysopedia` (private, xpltdco org).
No backend changes needed. No new dependencies. No risks.
## Recommendation
Use Vite's `define` config to inject `__APP_VERSION__`, `__BUILD_DATE__`, and `__GIT_COMMIT__` as build-time constants. Create a small `AppFooter.tsx` component that reads these globals. Style it to be subtle — muted text, small font, centered, matching the dark theme.
For the git commit SHA: use `child_process.execSync('git rev-parse --short HEAD')` in `vite.config.ts` (runs at build time, not runtime). This is a standard Vite pattern — no library needed.
The repo link should point to the GitHub repo but since it's private, make it conditional or just display the short SHA as a link to the commit page (useful for the admin who has access).
## Implementation Landscape
### Key Files
- `frontend/vite.config.ts` — Add `define` block with `__APP_VERSION__` (from package.json), `__BUILD_DATE__` (ISO string), `__GIT_COMMIT__` (short SHA from git). Import `child_process` and `package.json` at top of config.
- `frontend/src/vite-env.d.ts` — Add `declare const __APP_VERSION__: string` etc. for TypeScript.
- `frontend/src/components/AppFooter.tsx` — New component. Renders version, build date, optional GitHub link. ~20 lines.
- `frontend/src/App.tsx` — Import `AppFooter`, render `<AppFooter />` after `</main>` inside `.app`.
- `frontend/src/App.css` — Add `.app-footer` styles (~15 lines). Use existing CSS custom properties: `--color-text-muted` for text, `--color-border` for top border, `--color-bg-page` background (or transparent). Font size `0.75rem`.
### Build Order
1. **Vite config + type declarations** — Add build-time constants. This is the only non-trivial piece (importing package.json version, running git command). Prove it works with `npm run build` or checking `import.meta.env`.
2. **AppFooter component + CSS** — Straightforward UI. Uses the constants from step 1.
3. **Wire into App.tsx** — One import + one JSX line.
### Verification Approach
1. `cd frontend && npm run build` — confirms Vite build succeeds with the new `define` values (no TS errors, no runtime errors).
2. Visual check at `http://ub01:8096` after Docker rebuild — footer visible on Home, Topics, Technique pages. Shows version like `v0.1.0`, a build date, and a short commit SHA.
3. Verify footer is subtle — doesn't compete with content, uses muted colors, proper spacing.
## Constraints
- `vite.config.ts` runs in Node context during build, so `child_process.execSync` is fine there. But the `define`'d values must be JSON-stringifiable (wrap in `JSON.stringify`).
- The private GitHub repo link (`https://github.com/xpltdco/chrysopedia`) is only useful for the admin. This is a single-admin tool, so it's fine to show it.
- The Dockerfile.web uses a multi-stage build (`node:22-alpine``nginx:1.27-alpine`). The git info must be available during the `npm run build` step. Since the `COPY frontend/ .` step only copies the frontend dir, git metadata won't be available inside Docker. The build arg pattern (`ARG GIT_COMMIT`) must be used in `Dockerfile.web` and passed from `docker-compose.yml`, similar to how `Dockerfile.api` already does it (per M006/S03 commit `12f9fb7`).
## Common Pitfalls
- **Git not available in Docker build** — The Dockerfile only copies `frontend/`, so `git rev-parse` will fail. Must pass commit SHA as a build arg. Fallback to `'dev'` if git command fails (for local dev without git, or CI without .git).
- **`define` values not JSON.stringify'd** — Vite's `define` does literal text replacement. String values must be wrapped in `JSON.stringify()` or they'll be injected as bare identifiers, causing runtime errors.
- **Footer pushing content on short pages** — Use a non-sticky footer approach. The `.app` container should have `min-height: 100vh` with flexbox to push the footer to the bottom naturally, without overlapping content.

View file

@ -0,0 +1,40 @@
---
estimated_steps: 12
estimated_files: 5
skills_used: []
---
# T01: Add AppFooter with build-time version constants and Docker build arg plumbing
Create the AppFooter component showing app version, build date, and git commit SHA. Wire build-time constants through Vite define, add TS declarations, update App.tsx and App.css, and update Docker infrastructure to pass the git SHA into the web build.
**Important context:**
- The canonical repo is on ub01 at `/vmPool/r/repos/xpltdco/chrysopedia`. Docker/compose files live there.
- This working directory has the frontend code. Docker files (`docker/Dockerfile.web`, `docker-compose.yml`) must be edited on ub01 via SSH.
- The API Dockerfile already uses `ARG GIT_COMMIT_SHA=unknown` — follow the same pattern for the web Dockerfile.
- Vite `define` does literal text replacement, so string values MUST be wrapped in `JSON.stringify()`.
- `git rev-parse --short HEAD` won't work inside the Docker build (only `frontend/` is copied). Use `ARG VITE_GIT_COMMIT` and `ENV VITE_GIT_COMMIT=$VITE_GIT_COMMIT` so Vite can read it.
- The `.app` div in App.tsx has no CSS — add `display: flex; flex-direction: column; min-height: 100vh` so the footer is pushed to the bottom on short pages.
- The `vite.config.ts` runs in Node context — `child_process.execSync` and `fs.readFileSync` are available for local dev. For Docker builds, read from `process.env.VITE_GIT_COMMIT` with fallback to git command, with fallback to 'dev'.
- Package version comes from `package.json` — import it in vite.config.ts.
- Build date: `new Date().toISOString()` at build time.
- The repo link is `https://github.com/xpltdco/chrysopedia` (private, single-admin tool — fine to show).
## Inputs
- ``frontend/vite.config.ts` — current Vite config (no define block yet)`
- ``frontend/src/vite-env.d.ts` — currently just `/// <reference types="vite/client" />``
- ``frontend/src/App.tsx` — app shell with header + main, no footer`
- ``frontend/src/App.css` — all app styles, `.app` div has no CSS rules`
## Expected Output
- ``frontend/vite.config.ts` — updated with `define` block injecting `__APP_VERSION__`, `__BUILD_DATE__`, `__GIT_COMMIT__``
- ``frontend/src/vite-env.d.ts` — declares the three build-time constants for TypeScript`
- ``frontend/src/components/AppFooter.tsx` — new component rendering version, build date, commit link`
- ``frontend/src/App.tsx` — imports and renders `<AppFooter />` after `</main>``
- ``frontend/src/App.css``.app` flex column layout + `.app-footer` styles`
## Verification
cd frontend && npm run build 2>&1 | tail -5 && echo 'Build OK' && grep -q '__APP_VERSION__' vite.config.ts && grep -q 'AppFooter' src/App.tsx && test -f src/components/AppFooter.tsx && echo 'All checks pass'

View file

@ -0,0 +1,86 @@
---
id: T01
parent: S06
milestone: M006
provides: []
requires: []
affects: []
key_files: ["frontend/src/components/AppFooter.tsx", "frontend/vite.config.ts", "frontend/src/App.tsx", "frontend/src/App.css", "frontend/src/vite-env.d.ts"]
key_decisions: ["Used execSync for local git SHA with VITE_GIT_COMMIT env var fallback for Docker builds", "Read package.json version via fs.readFileSync to avoid TS module resolution issues in Vite config"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "Ran npm run build — Vite built successfully (48 modules, 781ms). Verified built JS bundle contains substituted values: version "0.1.0", date 2026-03-30T, and GitHub repo URL. All grep and file-existence checks pass."
completed_at: 2026-03-30T12:00:55.584Z
blocker_discovered: false
---
# T01: Added AppFooter component displaying app version, build date, commit SHA, and GitHub link — wired through Vite define constants with Docker-compatible env fallback
> Added AppFooter component displaying app version, build date, commit SHA, and GitHub link — wired through Vite define constants with Docker-compatible env fallback
## What Happened
---
id: T01
parent: S06
milestone: M006
key_files:
- frontend/src/components/AppFooter.tsx
- frontend/vite.config.ts
- frontend/src/App.tsx
- frontend/src/App.css
- frontend/src/vite-env.d.ts
key_decisions:
- Used execSync for local git SHA with VITE_GIT_COMMIT env var fallback for Docker builds
- Read package.json version via fs.readFileSync to avoid TS module resolution issues in Vite config
duration: ""
verification_result: passed
completed_at: 2026-03-30T12:00:55.585Z
blocker_discovered: false
---
# T01: Added AppFooter component displaying app version, build date, commit SHA, and GitHub link — wired through Vite define constants with Docker-compatible env fallback
**Added AppFooter component displaying app version, build date, commit SHA, and GitHub link — wired through Vite define constants with Docker-compatible env fallback**
## What Happened
Updated vite.config.ts with a define block injecting three build-time constants (__APP_VERSION__, __BUILD_DATE__, __GIT_COMMIT__) using JSON.stringify wrapping. Created AppFooter.tsx component rendering version, build date, commit link, and repo link. Updated App.tsx to render the footer and App.css with flex column layout to push footer to bottom.
## Verification
Ran npm run build — Vite built successfully (48 modules, 781ms). Verified built JS bundle contains substituted values: version "0.1.0", date 2026-03-30T, and GitHub repo URL. All grep and file-existence checks pass.
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `cd frontend && npm run build` | 0 | ✅ pass | 781ms |
| 2 | `grep -q '__APP_VERSION__' vite.config.ts` | 0 | ✅ pass | 10ms |
| 3 | `grep -q 'AppFooter' src/App.tsx` | 0 | ✅ pass | 10ms |
| 4 | `test -f src/components/AppFooter.tsx` | 0 | ✅ pass | 10ms |
## Deviations
None.
## Known Issues
None.
## Files Created/Modified
- `frontend/src/components/AppFooter.tsx`
- `frontend/vite.config.ts`
- `frontend/src/App.tsx`
- `frontend/src/App.css`
- `frontend/src/vite-env.d.ts`
## Deviations
None.
## Known Issues
None.

View file

@ -0,0 +1,35 @@
---
estimated_steps: 12
estimated_files: 2
skills_used: []
---
# T02: Update Docker build to pass git commit SHA to web image and deploy
Update Dockerfile.web and docker-compose.yml on ub01 to pass GIT_COMMIT_SHA as a build arg to the web service. Rebuild and deploy to verify the footer renders with real version info.
**Important context:**
- All Docker files are on ub01 at `/vmPool/r/repos/xpltdco/chrysopedia/`.
- Use SSH to edit files: `ssh ub01` then edit in `/vmPool/r/repos/xpltdco/chrysopedia/`.
- Current `docker/Dockerfile.web` is a two-stage build: `node:22-alpine` builds, `nginx:1.27-alpine` serves.
- Add `ARG VITE_GIT_COMMIT=dev` before the `RUN npm run build` line. Add `ENV VITE_GIT_COMMIT=$VITE_GIT_COMMIT` so Vite can read it via `process.env.VITE_GIT_COMMIT`.
- In `docker-compose.yml`, add `VITE_GIT_COMMIT: ${GIT_COMMIT_SHA:-dev}` to the web service's `build.args`.
- The API service already has `GIT_COMMIT_SHA: ${GIT_COMMIT_SHA:-unknown}` in its build args — follow the same pattern.
- After editing, commit the changes, then rebuild: `docker compose build chrysopedia-web && docker compose up -d chrysopedia-web`.
- After deploy, verify at http://ub01:8096 — the footer should be visible on the home page.
- If the commit SHA shows as 'dev', that's expected when `GIT_COMMIT_SHA` env var isn't set. To test with a real SHA: `GIT_COMMIT_SHA=$(git rev-parse --short HEAD) docker compose build chrysopedia-web`.
- Also push the T01 frontend changes to ub01 first — either git push from local + git pull on ub01, or copy files via SCP.
## Inputs
- ``docker/Dockerfile.web` — current two-stage build with no build args`
- ``docker-compose.yml` — web service section with no build args`
## Expected Output
- ``docker/Dockerfile.web` — updated with `ARG VITE_GIT_COMMIT` and `ENV VITE_GIT_COMMIT``
- ``docker-compose.yml` — web service build args include `VITE_GIT_COMMIT``
## Verification
ssh ub01 'cd /vmPool/r/repos/xpltdco/chrysopedia && grep -q VITE_GIT_COMMIT docker/Dockerfile.web && grep -q VITE_GIT_COMMIT docker-compose.yml && echo Docker files OK' && echo 'Verify footer at http://ub01:8096'

View file

@ -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 {

View file

@ -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() {
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</main>
<AppFooter />
</div>
);
}

View file

@ -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 (
<footer className="app-footer">
<span className="app-footer__version">
v{__APP_VERSION__}
</span>
<span className="app-footer__sep">·</span>
<span className="app-footer__date">
Built {__BUILD_DATE__.slice(0, 10)}
</span>
{commitUrl ? (
<>
<span className="app-footer__sep">·</span>
<a
href={commitUrl}
className="app-footer__commit"
target="_blank"
rel="noopener noreferrer"
>
{__GIT_COMMIT__}
</a>
</>
) : (
<>
<span className="app-footer__sep">·</span>
<span className="app-footer__commit">{__GIT_COMMIT__}</span>
</>
)}
<span className="app-footer__sep">·</span>
<a
href={REPO_URL}
className="app-footer__repo"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
</footer>
);
}

View file

@ -1 +1,5 @@
/// <reference types="vite/client" />
declare const __APP_VERSION__: string;
declare const __BUILD_DATE__: string;
declare const __GIT_COMMIT__: string;

View file

@ -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"}
{"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"}

View file

@ -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": {