hzhang 73da3926e7 feat(auth): admin_role config; drop manual admin-subject from wizard
OIDC settings page + setup wizard now configure the bootstrap admin
role instead of a hand-typed OIDC subject. The OIDC-only admin link is
handled automatically by the backend admin-role auto-connect on first
sign-in (explained inline in both the wizard and settings page).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:05:40 +01:00
2026-04-15 07:14:36 +01:00

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 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:<port>/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 <port>:127.0.0.1:<port> user@server). The UI health-checks http://127.0.0.1:<port>/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:<port>/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

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

npm run build        # tsc type-check, then vite build → dist/
npm run preview      # serve the production build locally

Test

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

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.

Description
HarborForge Frontend - React + TypeScript
Readme 852 KiB
Languages
TypeScript 87.3%
CSS 11.9%
Dockerfile 0.5%
HTML 0.3%