Skip to content

Repository files navigation

Ershu Frontend

中文

Ershu Frontend is the browser interface for Ershu. It covers the project workspace, analysis runs, documentation, search, the knowledge graph, MCP keys, and the admin console. It talks to Ershu only over public HTTP endpoints and holds no database credentials.

Where it fits

Browser
  │
  ├── /api/v1 ──> Ershu API
  │                login, projects, runs, documents, admin
  │
  └── /mcp/ ────> Ershu MCP
                   endpoint for developer agents

The frontend never connects to PostgreSQL, Redis, or Milvus, and it does not run code analysis itself. Pages create and read tasks through the API; workers process them in the background and the frontend only shows status and results. Permissions, state transitions, and security checks all live on the server. The UI hides controls based on role as a convenience, never as enforcement.

Features

Projects and runs

  • Project list, creation, overview dashboard, and settings. Settings (members, groups, grants, ownership transfer, deletion) are owner-only.
  • Repositories: add remote or local, credentials via the vault, webhooks, polling, and status. Requires can_manage.
  • Runs: full-project, single-repository, and incremental runs, with cancel, retry, and resume. Progress updates live.
  • Run detail: real-time timeline, agent messages with tool results, and token usage. Follow-up actions in chat or work mode.

Documents

  • Document tree grouped by repository, Markdown rendering, inline Mermaid diagrams with preview, and a section outline that stays in sync as you scroll.
  • Chat with your documents over a streaming connection. Answers come with citations and a source preview panel.

Search and graph

  • Code and document search in three modes: hybrid, semantic, and keyword. Filter by repository; results show chunk type, language, path, and line.
  • Interactive knowledge graph on a React Flow canvas. Focus on all, API, or dependency nodes; expand and collapse repositories, directories, and callables; filter by node or edge type.

Context tools

  • Resolve a project from a query string, run internal API code search, and query the graph in three modes: change surface, runtime flow, and free-form query.

MCP keys

  • Create, scope (all projects or selected ones), and revoke MCP API keys. The help dialog generates copy-ready config for six clients: Claude, Cline, OpenCode, Crush, Goose, and Roo.

Admin console

  • Users and groups.
  • Projects, with an admin view that respects the admin_readable flag, plus members and directory groups.
  • Model channels for LLM, embedding, and reranker models across OpenAI, OpenAI-compatible, DeepSeek, and Anthropic. Ping a channel to test it, refresh models from the provider catalog, and set pricing.
  • Vault for SSH keys and tokens.
  • Maintenance: reindex, index reconcile, run retention, stuck-run repair, and cache GC. Export and import documents and knowledge graphs. Dry-run is supported.
  • System settings editor with per-setting reset and dependency source management.
  • Audit log viewer with action and date filters.

The interface supports Chinese and English and light and dark themes. The sidebar collapses on narrow screens.

Tech stack

  • React 19 with react-router 7 (HashRouter)
  • Vite 7 for the dev server and production build
  • TypeScript 5.7 in strict mode, no emit (Vite builds)
  • Tailwind CSS 3 with CSS-variable theming and a .dark class toggle
  • Radix UI primitives wrapped as shadcn-style components (class-variance-authority, clsx, tailwind-merge)
  • lucide-react icons
  • React Flow (@xyflow/react) and d3-force for the knowledge graph
  • markdown-it, DOMPurify, and Mermaid for document rendering
  • @serenity-kit/opaque for OPAQUE password authentication
  • Self-hosted fonts: IBM Plex Sans and JetBrains Mono

No state-management library, HTTP client library, or chart library. Those are all written in this repo. Tests use Node's built-in test runner, not Jest or Vitest.

Project structure

Path What lives here
src/main.tsx, src/app.tsx Entry point, router, providers, lazy loading
src/pages/ Route-level page entry points
src/features/ Feature modules: pages, state, and tests grouped by capability
src/components/ui/ Base UI components
src/components/layout/ Global layout such as the sidebar
src/lib/api/ API client, types, request and error handling
src/lib/auth-context.tsx Login state and session restore
src/lib/i18n.ts Chinese and English routing and messages
src/lib/theme-provider.tsx Light, dark, and system theme
src/messages/ Chinese and English copy
src/hooks/ Real-time events and shared page behavior
src/styles/ Global, document, and chat Markdown styles
public/ Favicon and third-party license files

Prerequisites

  • Node.js 24 (see .nvmrc and engines in package.json)
  • npm
  • A running Ershu API for full integration
  • A running Ershu MCP when you check MCP client addresses

Install with a locked dependency tree:

cp .env.example .env.local
npm ci

Do not run npm install to change the lockfile casually. Only update package.json and package-lock.json for a deliberate upgrade, and explain why in the commit.

Run locally

npm run dev

The dev server listens on port 3000. Default URLs:

  • Chinese: http://localhost:3000/#/zh
  • English: http://localhost:3000/#/en

The dev proxy forwards two paths to local services:

  • /api to http://localhost:8000
  • /mcp to http://127.0.0.1:8001

If your API or MCP runs elsewhere, set it in .env.local:

VITE_DEV_API_PROXY=http://api-host:8000
VITE_DEV_MCP_PROXY=http://mcp-host:8001

Do not point /mcp at the API's port 8000.

Checking the integration

  1. Open both the Chinese and English home pages and confirm a language switch keeps you on the right page.

  2. Switch between light, dark, and system themes and confirm the choice survives a refresh.

  3. Check the API health endpoint:

    curl -fsS http://localhost:8000/health
  4. On first deploy, open http://localhost:3000/#/zh/setup-admin to create the first admin.

  5. After login, create or open a project and confirm the repository, run, document, search, and settings pages make successful requests.

A page shell loading is not the same as a working integration. The browser dev tools should not show persistent /api/v1 failures, CORS errors, or rejected cookies.

Configuration

Variable Default Scope Purpose
VITE_API_BASE_URL /api/v1 Build API prefix the browser calls
VITE_CSRF_COOKIE_NAME ershu_csrf Build CSRF cookie name, must match the API
VITE_DEV_API_PROXY http://localhost:8000 Dev server only Proxy target for /api
VITE_DEV_MCP_PROXY http://127.0.0.1:8001 Dev server only Proxy target for /mcp

VITE_API_BASE_URL and VITE_CSRF_COOKIE_NAME are baked into the static bundle at build time. Changing them requires a fresh npm run build.

Every VITE_ variable ends up in the browser bundle. Put only public addresses and non-sensitive options there. Never store tokens, passwords, or server secrets.

Model channels, repository credentials, and MCP keys are all stored by the backend. The frontend receives only what it needs to display and cannot bypass admin endpoints through environment variables.

Authentication and browser security

The frontend and API together handle:

  • OPAQUE password registration and login. The plaintext password never leaves the browser; only OPAQUE protocol messages do.
  • HttpOnly session cookies.
  • A browser-readable CSRF cookie paired with the X-Ershu-CSRF header on unsafe methods. If the API rejects a request with csrf_invalid, the client refreshes the token and retries once.
  • Session refresh timed from the access token's expiry and coordinated across tabs with navigator.locks.
  • On final auth failure, local state is cleared and the user is sent to the login page for the current language.

Before sending any password or OPAQUE credential, the client requires HTTPS (or localhost). Production should serve the page and API from the same origin over HTTPS. Cookie names, CORS, proxy origins, and CSRF settings must match the API.

The UI is not a permission boundary. Even when the page hides a control based on role, the server must check every request on its own.

Real-time updates

Two independent mechanisms, neither of them WebSocket.

Run progress uses the browser's EventSource on /runs/{id}/events with credentials. The connection reconnects automatically, closes on terminal events (completed, failed, cancelled), and falls back to a full snapshot if a server event cannot be parsed.

Document chat uses a ticketed fetch stream. The client posts to get a one-time ticket, then opens the stream with that ticket in a header (EventSource cannot carry custom headers). It parses the SSE frames itself and you can cancel mid-stream.

Testing, linting, and building

Tests run on Node's built-in runner with no test framework dependency:

npm test

Type-check and lint:

npm run lint

Format check:

npm run format:check

Production build:

npm run build

Test files live next to the source they cover (*.test.mts). They test pure logic such as form state, protocol parsing, API contracts, and graph layout. There are no component or DOM rendering tests. Before submitting, run all four commands above.

Tests must pass without reading backend source. When an endpoint changes, record the expected caller behavior in the frontend tests and treat the published contract or a running Ershu as the source of truth.

CI and releases

Two workflows live in .github/workflows/.

ci.yml runs on every push and pull request to main. It installs with npm ci, runs the tests, lints, checks formatting, and builds.

release.yml runs when you push a tag that starts with v. It runs the same checks, builds dist/, packages it as ershu-frontend-<tag>.tar.gz and ershu-frontend-<tag>.zip, and attaches both to a GitHub Release titled after the tag.

To cut a release:

git tag v1.0.1-20260810
git push origin v1.0.1-20260810

If something slipped through and you need to republish the same day, add a suffix:

git tag v1.0.1-20260810-2
git push origin v1.0.1-20260810-2

Routing and deployment

The build outputs to dist/:

npm run build

Preview the build locally:

npm run preview

The app uses HashRouter, so deep links live after the # in the URL and the server never sees them. You do not need a static-server fallback to index.html. Any static host can serve dist/ directly.

Assets are built for the site root, so deploy dist/ at the domain root. A reverse proxy in front must keep two same-origin prefixes:

/api/  -> API service
/mcp/  -> MCP service

The MCP proxy must preserve streaming responses and the raw /mcp/ path. The MCP help dialog builds client addresses from the current page origin, so if production is missing /mcp/, the copied configs will not work either.

FAQ

Every request returns 404 after the page loads

Check VITE_API_BASE_URL and the reverse proxy. The default request prefix is /api/v1, and the dev proxy matches /api.

CORS or cookie errors in local dev

Use the Vite same-origin proxy instead of hitting the API directly from the browser. If you must go cross-origin, the API's CORS_ORIGINS, secure cookies, and proxy headers must match the real page origin.

Sent back to the login page right after logging in

Check that the API is reachable, that cookies are being saved, that HTTPS and the Secure flag agree, and that the browser supports the session coordination the page relies on.

The MCP help address will not connect

The dialog uses the current page origin plus /mcp/. Confirm the dev proxy or production reverse proxy forwards that path to the MCP service on port 8001 and keeps the Authorization header.

The page did not change after editing .env.local

Restart the dev server. Production VITE_ variables need a fresh npm run build.

Chinese was updated but English still shows old copy

Every user-facing string has entries in both src/messages/zh.json and src/messages/en.json. Update both, then check /#/zh and /#/en separately.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages