Skip to main content
The Effect Coffee Shop browser frontend lives in the ui/ Bun workspace. It is a React 19 single-page application built with Vite and TypeScript, served by the same Bun monorepo that hosts the Effect-based backend. The UI exposes two distinct experiences: a customer-and-barista control room for order management, and a browser-side AI assistant that connects to the backend MCP server.

Tech stack

LayerLibrary / version
UI frameworkReact 19, TypeScript
Build toolVite 8 with @vitejs/plugin-react
StylingTailwind CSS 4 via @tailwindcss/vite
Component primitivesRadix UI (@radix-ui/react-dialog, @radix-ui/react-select, @radix-ui/react-progress, …)
High-level componentsshadcn/ui conventions inside ui/src/shared/ui/retroui/
Data fetchingTanStack Query v5 (@tanstack/react-query)
In-browser AI@huggingface/transformers + onnxruntime-web
Effect runtimeeffect 4.0.0-beta (used for async orchestration in the assistant)
Toast notificationsSonner
IconsLucide React

Feature areas

The app is split into two features under ui/src/features/:

coffee-shop

The customer-and-barista control room. Handles menu display, order composition, real-time queue tracking, and per-ticket lifecycle actions. Served at /control-room (alias /coffee-shop).

assistant

The Browser Barista Brain. Loads LiquidAI’s LFM2.5-350M ONNX model in the browser over WebGPU, then lets the model call the backend MCP server for live coffee actions. Served at /.
The root App component (ui/src/app/App.tsx) performs a lightweight pathname switch — no router library is needed:
export default function App() {
  const pathname = window.location.pathname;
  if (pathname === appRoutes.home) {
    return <BrowserMcpLandingPage />;
  }
  if (isControlRoomPath(pathname)) {
    return <CoffeeShopPage />;
  }
  return <NotFoundPage />;
}

Running locally

1

Install dependencies

From the monorepo root, a single install covers both workspaces:
bun install
2

Start the backend

The frontend dev server proxies /api and /mcp to http://localhost:3000 by default, so the backend must be running first:
bun run http
3

Start the frontend dev server

bun run --cwd ui dev
Vite starts on its default port. The shared Vite config (ui/vite.shared.ts) registers two dev-server proxies:
PrefixProxy target
/api$VITE_COFFEE_PROXY_TARGET (default http://localhost:3000), path prefix stripped
/mcp$VITE_COFFEE_PROXY_TARGET, path kept as-is
Both proxies enable changeOrigin and WebSocket upgrade (ws: true).
4

Open the app

Navigate to http://localhost:5173 (or whichever port Vite chose). The home route loads the AI assistant; /control-room loads the coffee shop.
You can also start both the backend and the frontend together from the repo root with bun run dev. Turborepo runs both tasks in parallel and reuses its .turbo cache on repeated runs.

Storybook

Storybook 10 is configured at ui/.storybook/ using @storybook/react-vite. It picks up all *.stories.{ts,tsx} files under ui/src/ and shares the same Vite configuration as the app build (Tailwind included). Enabled addons: @storybook/addon-docs, @storybook/addon-a11y, @storybook/addon-vitest.
# Serve stories (port 6006)
bun run storybook

# Build static output to ui/storybook-static/
bun run build-storybook

Portless subdomain routing

For HTTP-only local development without port numbers, the project ships dev:onion:api and dev:onion:ui scripts that use Portless.
1

Install Portless globally

bun add -g portless
2

Start the backend on its subdomain

bun run dev:onion:api
The REST API and MCP HTTP endpoint are reachable at http://api.onion.localhost:1365.
3

Start the frontend on its subdomain

bun run dev:onion:ui
The frontend UI is reachable at http://onion.localhost:1365.
This project runs Portless on a dedicated port (1365) with an isolated state directory at /tmp/effect-v4-onion-portless, so it does not interfere with any other Portless daemon you have running. .localhost resolution works in Chrome, Firefox, and Edge without requiring sudo.

Building for production

bun run build
Turborepo orchestrates the build across workspaces and caches the output. To build only the work affected by the current branch:
bun run build:affected

Build docs developers (and LLMs) love