Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
054d1a8
refactor(api): move routers behind /api prefix
Leolebleis May 2, 2026
7357b08
feat(chat): add ModelMessage → ThreadMessageLike projection
Leolebleis May 2, 2026
515dc73
feat(chat): expose projected ThreadMessageLike in replay response
Leolebleis May 2, 2026
d959548
feat(events): add BagActivated, BagFinished, WaterRefilled
Leolebleis May 2, 2026
43d1dc1
feat(events): publish BagActivated, BagFinished, WaterRefilled
Leolebleis May 2, 2026
e2a707a
feat(events): broadcast status events over /api/events SSE
Leolebleis May 2, 2026
c475277
feat(api): mount frontend/dist as SPA root
Leolebleis May 2, 2026
b27d7a0
feat(frontend): scaffold Vite + React + TS app under frontend/
Leolebleis May 2, 2026
fec7d83
feat(frontend): theme system + palette CSS vars + Tailwind v4
Leolebleis May 2, 2026
2608729
feat(frontend): API client + SSE helpers
Leolebleis May 2, 2026
c90572a
feat(frontend): chat zustand store with streaming-aware mutations
Leolebleis May 2, 2026
dd54748
feat(frontend): chat runtime adapter — SSE → store + assistant-ui
Leolebleis May 2, 2026
ffe39e8
feat(frontend): initial chat thread replay from server projection
Leolebleis May 2, 2026
3ae4041
feat(frontend): generic primitives — Button, Sheet, Toast
Leolebleis May 2, 2026
4e6aba4
feat(frontend): wire chat surface end-to-end in App shell
Leolebleis May 2, 2026
0bf4794
feat(frontend): status hooks + SSE event → query invalidation router
Leolebleis May 2, 2026
e52fc4c
feat(frontend): status header component
Leolebleis May 2, 2026
6e3d0d0
feat(frontend): brew-now sheet with pre-flight gating
Leolebleis May 2, 2026
c87678e
feat(frontend): post-brew rating toast
Leolebleis May 2, 2026
6126f13
feat(frontend): wire status header, brew-now sheet, rating toast into…
Leolebleis May 2, 2026
23c8003
build: multi-stage Dockerfile with frontend build
Leolebleis May 2, 2026
3305d8e
build: forward FELLOW_API_KEY as VITE_FELLOW_API_KEY build arg
Leolebleis May 2, 2026
e98a07c
ci: add frontend lint/typecheck/test/build job
Leolebleis May 2, 2026
c483cd0
test(e2e): playwright config + theme persistence spec
Leolebleis May 2, 2026
7ef3b20
refactor: simplify after review pass
Leolebleis May 2, 2026
6701691
fix(ci): unblock red checks
Leolebleis May 2, 2026
361558e
fix(frontend): restore TextPart wrapper + make typecheck actually typ…
Leolebleis May 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,23 @@ jobs:
with:
files: ./coverage.xml
fail_ci_if_error: false

frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run test
- run: npm run build
env:
VITE_FELLOW_API_KEY: ci-build-fake-key
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ htmlcov/
docs/superpowers/
.superpowers/
docker-compose.override.yml

# frontend - build artifacts and local env stay ignored
frontend/node_modules/
frontend/dist/
frontend/.env.local
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Stage 1 — frontend build
FROM node:22-alpine AS frontend-build
WORKDIR /app/frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci
COPY frontend/ ./
ARG VITE_FELLOW_API_KEY
ENV VITE_FELLOW_API_KEY=${VITE_FELLOW_API_KEY}
RUN npm run build

# Stage 2 — Python builder
FROM python:3.13-slim AS builder

RUN apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/*
Expand All @@ -10,14 +21,17 @@ RUN uv sync --frozen --no-dev --no-install-project
COPY src/ src/
RUN uv sync --frozen --no-dev --no-editable

# Stage 3 — runtime
FROM python:3.13-slim

RUN useradd --create-home appuser
USER appuser
WORKDIR /app

COPY --from=builder /app/.venv /app/.venv
COPY --from=frontend-build /app/frontend/dist /app/frontend/dist
ENV PATH="/app/.venv/bin:$PATH"
ENV BREW_FRONTEND_DIST=/app/frontend/dist

EXPOSE 8000

Expand Down
5 changes: 4 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
services:
brew:
build: .
build:
context: .
args:
VITE_FELLOW_API_KEY: ${FELLOW_API_KEY}
container_name: brew
restart: unless-stopped
env_file: .env
Expand Down
2 changes: 2 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
VITE_FELLOW_API_KEY=replace-with-actual-key-or-build-arg
VITE_API_BASE=/api
27 changes: 27 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

playwright-report/
test-results/
73 changes: 73 additions & 0 deletions frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)

## React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...

// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,

// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
11 changes: 11 additions & 0 deletions frontend/e2e/theme.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, expect } from "@playwright/test";

test("theme toggle persists across reload", async ({ page }) => {
await page.goto("/");
const toggle = page.getByRole("button", { name: /theme/i });
await toggle.click(); // → light
await toggle.click(); // → dark
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
await page.reload();
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
});
22 changes: 22 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
globals: globals.browser,
},
},
])
13 changes: 13 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading