# HarborForge.Frontend Single-page web client for HarborForge — the agent/human collaborative task-management platform. It provides the operator UI for projects, milestones, tasks, proposals, calendar/meetings, users, roles and the live monitor, and walks operators through first-time provisioning via the SSH-tunnel Setup Wizard. Part of the [HarborForge](../README.md) platform. ## Tech Stack - **React 18** (`react`, `react-dom`) - **TypeScript 5** - **Vite 5** dev server / bundler (`@vitejs/plugin-react`) - **react-router-dom 6** for client-side routing - **axios** for HTTP, with a shared request/response interceptor layer - **dayjs** for date handling - **Vitest 4** + Testing Library (`@testing-library/react`, `jsdom`) for unit tests Path alias `@` → `src/` (configured in `vite.config.ts` and `tsconfig.json`). ## Pages Routes are declared in `src/App.tsx`. The app gates rendering on three states — `checking` (probing configuration), `setup` (Setup Wizard), and `ready` (authenticated app). `/monitor`, `/login`, `/roles` and `/users` are reachable without an authenticated session; everything else requires login. | Route | Page | Purpose | |---|---|---| | `/` | `DashboardPage` | Landing dashboard | | `/tasks` | `TasksPage` | Task list / filtering | | `/tasks/:taskCode` | `TaskDetailPage` | Task detail, transitions, comments | | `/projects` | `ProjectsPage` | Project list | | `/projects/:id` | `ProjectDetailPage` | Project detail / editor | | `/milestones` | `MilestonesPage` | Milestone list | | `/milestones/:milestoneCode` | `MilestoneDetailPage` | Milestone detail / actions | | `/proposals` (`/proposes`) | `ProposalsPage` | Proposal list | | `/proposals/:proposalCode` (`/proposes/:proposalCode`) | `ProposalDetailPage` | Proposal detail, accept/reject/reopen | | `/calendar` | `CalendarPage` | Calendar view | | `/meetings/:meetingCode` | `MeetingDetailPage` | Meeting detail | | `/supports/:supportCode` | `SupportDetailPage` | Support request detail | | `/notifications` | `NotificationsPage` | Notification inbox | | `/roles` | `RoleEditorPage` | Role / permission (RBAC) editor | | `/users` | `UsersPage` | User management | | `/monitor` | `MonitorPage` | Live system monitor (public) | | `/login` | `LoginPage` | Authentication | | _(state-gated)_ | `SetupWizardPage` | First-run provisioning wizard | Shared building blocks live in `src/components/` (`Sidebar`, `CreateTaskModal`, `MilestoneFormModal`, `ProjectFormModal`, `CopyableCode`); the auth hook is `src/hooks/useAuth.ts`; shared types are in `src/types/index.ts`. ## How It Talks to the Backend All API access goes through the shared axios instance in `src/services/api.ts`: - The base URL is **resolved at runtime** from `localStorage['HF_BACKEND_BASE_URL']` (re-read on every request via an axios request interceptor), not baked in at build time. - A bearer token from `localStorage['token']` is attached automatically when present. - A response interceptor clears the token and redirects to `/login` on HTTP `401`, except on the public paths `/monitor` and `/login`. Authentication (`src/hooks/useAuth.ts`) posts form-encoded credentials to `/auth/token`, stores the returned `access_token`, and loads the current user from `/auth/me`. In local dev, `vite.config.ts` also proxies `/api/*` to `http://backend:8000` (stripping the `/api` prefix) for same-origin development against a Compose backend. ## Setup Wizard / SSH-Tunnel Flow On startup `App.tsx` runs `checkInitialized()`: 1. It calls the backend `GET /config/status`. If that returns `initialized: true`, the app is `ready` (and `backend_url`, if present, is cached into `localStorage['HF_BACKEND_BASE_URL']`). 2. Otherwise, if a wizard port was previously saved (`localStorage['HF_WIZARD_PORT']`), it tries `http://127.0.0.1:/api/v1/config/harborforge.json` directly. 3. If neither indicates an initialized install, it renders `SetupWizardPage`. The Setup Wizard (`src/pages/SetupWizardPage.tsx`) is a 4-step flow — **Wizard → Admin → Backend → Finish**: - **Wizard**: the operator enters the local port that an SSH tunnel forwards to **AbstractWizard** (`ssh -L :127.0.0.1: user@server`). The UI health-checks `http://127.0.0.1:/health` and stores the port. - **Admin**: collects the first admin account (username, password, email, full name). - **Backend**: optional backend base URL override. - **Finish**: writes the assembled config to AbstractWizard via `PUT http://127.0.0.1:/api/v1/config/harborforge.json`, caches the backend URL locally, and instructs the operator to `docker compose restart` on the server before refreshing. ## Local Development ```bash npm install npm run dev # Vite dev server on http://0.0.0.0:3000 ``` The dev server proxies `/api` to `http://backend:8000` and allows the hosts `frontend`, `127.0.0.1`, `localhost`. ### Build ```bash npm run build # tsc type-check, then vite build → dist/ npm run preview # serve the production build locally ``` ### Test ```bash npm test # vitest run (jsdom) npm run test:watch # watch mode npm run test:ui # Vitest UI ``` Tests live in `src/test/**/*.test.{ts,tsx}` (e.g. `calendar.test.tsx`, `proposal-essential.test.tsx`) with global setup in `src/test/setup.ts`. ### Docker ```bash docker build -t harborforge-frontend . docker run -p 3000:3000 harborforge-frontend ``` The multi-stage `Dockerfile` builds with Node 20 and serves `dist/` via `serve` on port 3000. Set `FRONTEND_DEV_MODE=1` to run the Vite dev server inside the container instead of serving the static build. ## Environment / Configuration - **`VITE_API_BASE`** (`.env`, default `http://backend:8000`) — a build hint for the default backend location. - The effective backend base URL is determined at **runtime** from `localStorage['HF_BACKEND_BASE_URL']`, which is populated by the Setup Wizard or by the backend's `/config/status` response — so changing the backend target does not require a rebuild. - `localStorage['HF_WIZARD_PORT']` — last AbstractWizard tunnel port. - `localStorage['token']` — current JWT bearer token. ## Theming The entire visual design is a single self-contained design system in **`src/index.css`** — there is no CSS-in-JS, no Tailwind, and no per-component stylesheets. Components only reference global CSS classes; all colors, typography, spacing and effects are driven by CSS custom properties on `:root`. To re-theme the app, edit this one file. The theme is **"Foundry Deck"** — a shipwright's drafting table meets a foundry control panel: - **Blackened-steel dark palette** — cool-tinted near-black surfaces (`--bg`, `--bg-card`, `--bg-hover`, `--bg-sink`) with machined, tight-radius borders. - **Molten-ember accent** — a single hot orange accent (`--accent`, `--accent-hover`, `--ember` gradient, ember glow) used sparingly as the signature element. - **Oxidized-steel secondary** plus a forge-mapped status palette (patina success, heat-amber warning, rust danger, arc violet). - **Blueprint-grid background** — layered radial ember/steel vignettes, a fine 40px draughting grid, and a subtle SVG film-grain overlay. - **Technical web fonts** loaded via a Google Fonts `@import` at the top of `src/index.css`: **Saira Condensed** (headings), **Hanken Grotesk** (body), and **JetBrains Mono** (code/monospace). Selectors are kept 1:1 with the existing markup, so theming changes are made entirely in `src/index.css` via the CSS variables and global classes.