feat(ui): scaffold vite + react 19 frontend

Replaces the CRA + React 16 + class-component frontend with the
shape from architecture/generic.md §4: vite + react + swc + ts,
served as static from nginx in prod, vite dev server in dev with
/api proxied to localhost:8080.

Layout:
  ui/
    package.json, vite.config.ts, tsconfig.{json,app,node}.json
    index.html
    src/
      main.tsx           — react root + react-query provider
      App.tsx            — header, filters, vertical timeline
      App.css            — dark backdrop, hot-pink links
      api/client.ts      — TS types mirroring moments-entities;
                            fetchEvents, fetchSources via /api/v1
      components/
        Filters.tsx      — source toggles, count slider, date range
        TimelineEntry.tsx — renders one TimelineItem with body
                             support for markdown, commits, links
      lib/icon.tsx       — TimelineIcon → react-bootstrap-icons map
                            + colour per icon

Stack: react 19, @tanstack/react-query 5, react-bootstrap 2 (on
bootstrap 5), react-vertical-timeline-component 3, rc-slider 11
(<Slider range /> replaces the removed v8 Range), react-markdown 9.

Dev proxy: /api/* → http://localhost:8080/* (rewrite strips /api).
Backend stays location-agnostic at /v1; ingress prefix is added
by nginx (and the dev proxy) so the same fetch shape works in
both environments.

Verified: tsc -b clean, vite build clean (417 KB js / 245 KB css
gzip 128 / 33), vite dev server serves the index. NOT verified
visually in a browser — that's a `pnpm run dev` away on roosta
once the api is up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 19:18:32 +03:00
parent 7772393598
commit b04afd83f9
15 changed files with 2656 additions and 0 deletions

56
ui/src/lib/icon.tsx Normal file
View File

@@ -0,0 +1,56 @@
import {
ArrowLeftRight,
ArrowUpCircle,
ArrowsAngleContract,
Bug,
ChatLeft,
CodeSquare,
DashCircle,
Diagram3,
ExclamationCircle,
PlusCircle,
StarFill,
Tag,
Wrench,
} from 'react-bootstrap-icons';
import type { TimelineIcon } from '../api/client';
const map: Record<TimelineIcon, typeof Wrench> = {
'git-push': ArrowUpCircle,
'git-commit': CodeSquare,
'git-merge': ArrowsAngleContract,
'git-fork': Diagram3,
'git-branch-create': PlusCircle,
'git-branch-delete': DashCircle,
'pull-request': ArrowLeftRight,
issue: ExclamationCircle,
comment: ChatLeft,
star: StarFill,
release: Tag,
bug: Bug,
generic: Wrench,
};
const colors: Record<TimelineIcon, string> = {
'git-push': '#2e7d32',
'git-commit': '#1565c0',
'git-merge': '#6a1b9a',
'git-fork': '#1565c0',
'git-branch-create': '#2e7d32',
'git-branch-delete': '#c62828',
'pull-request': '#1565c0',
issue: '#ef6c00',
comment: '#1565c0',
star: '#f9a825',
release: '#6a1b9a',
bug: '#c62828',
generic: '#546e7a',
};
export function iconFor(name: TimelineIcon) {
return map[name] ?? Wrench;
}
export function colorFor(name: TimelineIcon) {
return colors[name] ?? colors.generic;
}