bobbylite / bobbylite/service-template

Add UI

Đang mở
#7 0 bình luận 0 reaction 0 người được giao Xem trên GitHub
enhancement priority: high
Ngôn ngữ chính
Python
Star
0
Fork
0
Chỉ số merge pull request
Không có pull request nào được merge trong 30 ngày

Mô tả

# Task Brief for Claude Code: Service Template UI

Companion to `NOTES.md`, which specifies the FastAPI + MCP backend. This
brief covers the **frontend**. Read both — the identity seam described in
`NOTES.md` (`src/app/identity/provider.py`) is the backend half of the auth
story specified here.

## Goal

Build a **generic, reusable dashboard UI template** for the service backend.
Like the backend, it must be clonable and renamable for unrelated projects —
no project-specific branding baked into the logic.

Three surfaces:

1. **Agent chat** — conversational UI against the backend's MCP tools
2. **Admin dashboard** — operational overview
3. **Metrics tab** — gauges and charts, heavily animated

Cross-cutting: light + dark theming, pluggable identity providers, and a
local-development switch that bypasses login.

## Stack

Versions verified 2026-08-31. **Re-check with `npm view version`
before pinning** — do not trust this table blindly if months have passed.

| Package | Version | Role |
| --- | --- | --- |
| `vite` | 8.2.2 | Build tool |
| `react` / `react-dom` | 19.2.8 | UI runtime |
| `typescript` | 7.0.2 | Types (the native/Go compiler — much faster) |
| `tailwindcss` | 4.3.3 | Utilities, CSS-first `@theme` config |
| `@tanstack/react-query` | 5.102.8 | Server state, caching, polling |
| `oidc-client-ts` | 3.5.0 | OIDC protocol layer |
| `react-oidc-context` | 3.3.1 | React bindings for the above |
| `motion` | 13.1.1 | JS animation — **only** where CSS genuinely can't |
| `recharts` | 3.10.1 | Line/bar/area charts |

**Use a Vite SPA, not Next.js.** The backend is Python; adding a Node SSR
runtime buys nothing here. The build output is static assets that FastAPI can
serve directly (or a CDN can). Pick React Router or TanStack Router for
routing — verify the current major version at install time.

## Design system

### Token architecture

One set of semantic tokens, defined once, resolved per theme. Use native
`light-dark()` so most tokens need a single declaration:

```css
@layer tokens {
:root {
color-scheme: light dark;

/* Surfaces */
--surface-canvas: light-dark(#ffffff, #0d1117);
--surface-raised: light-dark(#ffffff, #161b22);
--surface-inset: light-dark(#f6f8fa, #010409);

/* Text */
--fg-default: light-dark(#1f2328, #e6edf3);
--fg-muted: light-dark(#59636e, #8b949e);

/* Lines */
--border-default: light-dark(#d1d9e0, #30363d);

/* Semantic accents */
--accent: light-dark(#0969da, #2f81f7);
--success: light-dark(#1a7f37, #3fb950);
--danger: light-dark(#cf222e, #f85149);
--attention: light-dark(#9a6700, #d29922);
--done: light-dark(#8250df, #a371f7);
}
}
```

Theme selection is three-state: `light`, `dark`, `system`. `system` sets no
attribute and lets `color-scheme` resolve it; explicit choices stamp
`data-theme` on `` and override via `:root[data-theme="dark"]`.
Persist the choice in `localStorage`, wrapped in try/catch, and render
correctly when nothing is stored.

**Never** define a color only inside a media query or `[data-theme]` block.

### Dark theme: GitHub

Dark mode follows GitHub's Primer palette — the hex values above are Primer's
dark tokens. The character to reproduce: cool near-black layered surfaces
(`#0d1117` page, `#161b22` raised cards), low-chroma borders (`#30363d`) that
separate by value rather than contrast, and **accent color reserved for
meaning** — blue for navigation and links, green/red/yellow/purple only for
status semantics. Never decorative.

Light mode is a clean neutral companion, not a GitHub clone.

### Agent chat: Google/Material styling

The chat surface deliberately departs from the GitHub aesthetic. It should
read as Material:

- **Flat color fills.** No gradients, no glassmorphism. Solid tonal surfaces.
- **Raised buttons.** Real elevation via layered shadows, a visible lift on
hover, and a press-down on `:active`. Include a ripple on press — do it
with CSS (a pseudo-element scaling from the pointer position set via a
custom property) rather than a library.
- **Raised inputs.** Elevated filled text fields with floating labels that
animate to the top-left on focus/fill. Use `:placeholder-shown` and `:has()`
for the label state — no JS state tracking for a purely visual concern.
- Generous corner radii, deliberate 4px-grid spacing, one type scale.

Elevation tokens must be theme-aware: in dark mode shadows read weakly, so
elevated surfaces also step up in lightness, exactly as Material specifies.

## Layout

```
┌────────────┬───────────────────────────────────┐
│ │ Top bar: title, theme switch, │
│ Sidebar │ user menu │
│ ├───────────────────────────────────┤
│ · Chat │ │
│ · Admin │ Route outlet │
│ · Metrics │ │
└────────────┴───────────────────────────────────┘
```

Collapsible sidebar. The shell is responsive via **container queries**, not
viewport media queries — panels adapt to their own space so they stay correct
when embedded or resized.

## Admin dashboard

Generic and driven by backend data, not hardcoded:

- Service health from `GET /health` (status, uptime, version), polled with
TanStack Query
- Discovered MCP tools — list what the server advertises, with schemas
- Recent activity / request log table with sorting and filtering
- Session and identity info: which IDP authenticated the user, claims,
token expiry

## Metrics tab

Where the animation work is most visible.

**Gauges: hand-rolled, not from a chart library.** A radial gauge is a
`conic-gradient` whose sweep is a registered custom property, which makes it
animatable and interpolatable:

```css
@property --gauge-angle {
syntax: "";
inherits: false;
initial-value: 0deg;
}

.gauge {
background: conic-gradient(
from -90deg,
var(--accent) var(--gauge-angle),
var(--surface-inset) 0
);
transition: --gauge-angle 600ms cubic-bezier(0.2, 0, 0, 1);
}
```

Use Recharts only for conventional line/bar/area series.

### Required modern CSS techniques

Use these deliberately — they are the point of the brief. Verify current
browser support before relying on any of them, and degrade gracefully:

- **`@property`** — typed custom properties, so gradients, angles and numbers
animate at all (the foundation of the gauges)
- **Scroll-driven animations** — `animation-timeline`, `scroll()`, `view()`
to reveal metric cards on scroll with zero JS scroll listeners
- **View Transitions** — `view-transition-name` for route changes and for
cards morphing into detail panels. Use the same-document API; cross-document
is still not in Firefox.
- **`sibling-index()`** — stagger card entrances without per-element inline
delays: `transition-delay: calc((sibling-index() - 1) * 40ms)`
- **`@starting-style`** — entry transitions for toasts, popovers and newly
streamed chat messages, with no first-frame flash
- **`if()`** — inline conditionals, ideal for honoring reduced motion:
`transition-duration: if(media(prefers-reduced-motion: reduce): 0ms; else: 180ms;)`
- **Anchor positioning** — `anchor-name` / `position-anchor` for tooltips,
menus and popovers, with no JS rect measurement
- **Container queries** — including **scroll-state queries** for styling
stuck headers
- **`@scope`** — scope the Material chat styles so they cannot leak into the
GitHub-themed shell
- **Popover API** — native `popover` for menus and dialogs
- **`oklch()` + `color-mix()`** — derive hover/active/disabled variants from
base tokens instead of hand-picking hexes
- **`text-wrap: balance`** on headings, `pretty` on body copy
- **`field-sizing: content`** — the chat composer grows with its content, no
JS autosize

**Motion is a feature, not decoration.** Every animation must respect
`prefers-reduced-motion: reduce`. Animate `transform`, `opacity` and
registered custom properties — never layout properties.

## Authentication: multiple IDPs

Generic OIDC. **Never hardcode a provider.** Any compliant IDP — Entra ID,
Okta, Auth0, Keycloak, Google, Cognito — must work by configuration alone,
using discovery (`/.well-known/openid-configuration`).

Use **Authorization Code + PKCE**, no implicit flow, no client secret in the
browser.

```ts
export interface IdpConfig {
id: string; // "corp-okta"
displayName: string; // rendered on the login button
authority: string; // issuer URL; metadata comes from discovery
clientId: string;
scopes: string[];
}
```

Configuration comes from the environment at build time, or better, from a
runtime `GET /config` endpoint so one build can be promoted across
environments. Multiple configured IDPs render as a provider-picker on the
login screen; a single one skips the picker.

Mirror the backend's seam: define an `AuthProvider` interface, put the OIDC
implementation behind it, and have components depend on the interface. Adding
SPIFFE/SPIRE or swapping providers later must not touch feature code.

### Local development bypass

A config switch that skips login entirely and injects a fake authenticated
user, so the UI can be worked on without an IDP.

**This is the most dangerous thing in the brief. It must fail closed:**

- Driven by an env flag (e.g. `VITE_AUTH_BYPASS`) that is honored **only**
when the Vite mode is not `production`
- Dead-code-eliminated from production bundles — gate on a compile-time
constant so the bypass branch is not even present in a prod build
- A build-time assertion that **fails the build** if the flag is set while
building for production
- Loud, permanent, unmissable in-app banner whenever it is active
- The fake user must exercise the same `AuthProvider` interface as real OIDC,
so bypass and real mode cannot drift

## Directory layout

```
ui/
├── package.json
├── vite.config.ts
├── tsconfig.json
├── .env.example
├── index.html
└── src/
├── main.tsx
├── app/
│ ├── router.tsx
│ └── providers.tsx # query client, auth, theme
├── styles/
│ ├── tokens.css # semantic tokens, both themes
│ ├── base.css # reset, typography
│ └── motion.css # keyframes, scroll timelines
├── auth/
│ ├── types.ts # AuthProvider interface — the seam
│ ├── oidc-provider.tsx # real OIDC implementation
│ ├── bypass-provider.tsx # dev-only, fails closed
│ └── idp-config.ts
├── components/ # generic, presentational
│ ├── ui/ # Button, Input, Card, Dialog...
│ └── chat/ # Material-styled chat surface
├── features/
│ ├── chat/
│ ├── admin/
│ └── metrics/ # gauges, charts, animated tiles
├── lib/
│ ├── api-client.ts # typed fetch against the backend
│ └── mcp-client.ts # MCP tool discovery + invocation
└── hooks/
```

Keep `components/` generic and `features/` composed from it. Feature code
must never reach for a raw color — only tokens.

## Accessibility

Not optional, and not a later pass:

- Both themes meet WCAG AA contrast — verify, don't assume
- Full keyboard navigation; visible focus rings using `:focus-visible`
- Chat streams into an appropriate live region
- Gauges and charts expose accessible text alternatives; color is never the
sole carrier of meaning
- Respect `prefers-reduced-motion` and `prefers-contrast`

## Testing

- **Vitest** + **React Testing Library** for components and hooks
- **Playwright** for flows: login via a mock OIDC provider, theme switching,
chat round-trip
- Explicit tests that the bypass provider is absent from a production build
and that both themes render every token

## Non-goals

- No component library (MUI, shadcn, Chakra) — the styling *is* the work here
- No backend changes; consume the existing endpoints
- No real IDP tenant setup; ship config and docs
- No i18n yet, but do not hardcode strings in a way that blocks it later

Hướng dẫn đóng góp

Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này

Hướng nghiên cứu

The issue describes building a complete frontend dashboard with three surfaces (chat, admin, metrics) from scratch. Start by reading the detailed brief and the companion NOTES.md. The work involves setting up a Vite SPA with React, implementing a design system with CSS custom properties, creating multiple feature areas, and integrating authentication. The directory layout in the brief shows where to place files. 'Done' means a fully functional, themed UI that consumes the existing backend endpoints.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
react, tailwindcss, typescript, vite
Lĩnh vực
frontend, web-dev
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
30/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.