view hg-web/README.md @ 243:823f2a8b16c8

[repo] Ignore Vim swap files
author MrJuneJune <me@mrjunejune.com>
date Mon, 03 Aug 2026 15:26:44 -0700
parents 8bb0ac8f4587
children c5129452493e
line wrap: on
line source

# hg-web

A custom Mercurial forge and repository browser. The application keeps the
networking and HTTP stack in C with Seobeo, while the hand-drawn web interface
is implemented in React and TypeScript.

## Wiki tree

```text
hg-web
├── Runtime
│   ├── hg_web_server :6970
│   │   ├── React application and static assets
│   │   ├── repository browsing API
│   │   ├── commit graph API
│   │   └── Mercurial wire-protocol proxy
│   └── hg serve :4444
│       ├── repository files and graph data
│       └── Mercurial pull/push protocol
├── Backend
│   ├── main.c
│   └── ../seobeo
│       ├── s_network.c
│       ├── s_web.c
│       ├── s_http_client.c
│       └── s_ssl.c
├── Frontend
│   └── src
│       ├── main.tsx
│       ├── components
│       │   ├── app.tsx
│       │   ├── directory-browser.tsx
│       │   ├── graph.tsx
│       │   ├── header.tsx
│       │   ├── footer.tsx
│       │   ├── theme.tsx
│       │   └── repo-browser.tsx
│       ├── index.html
│       ├── index.css
│       ├── base.css
│       └── custom pencil, panda, and icon assets
├── Build
│   ├── BUILD
│   ├── ../gui_ze/gui_ze.bzl
│   ├── ../markdown_converter
│   └── ../third_party/highlight
└── Operations
    └── deploy.sh
```

`repo-browser.tsx` is an older standalone browser implementation. The active
entry point is `main.tsx`, which renders `app.tsx`; `app.tsx` embeds
`directory-browser.tsx`.

## Runtime topology

```text
Browser
  |
  | HTTPS
  v
nginx
  |
  | HTTP :6970
  v
hg_web_server (main.c + Seobeo)
  |
  | HTTP :4444
  v
hg serve (Zenbu Mercurial repository)
```

The C server owns the public routes. It serves the application shell and
assets, translates browser API requests into `hg serve` requests, and streams
Mercurial wire-protocol traffic without routing bundle data through the normal
buffered HTTP response path.

## Request flows

### Application shell

```text
GET /, /directories, or /graph
  -> GetReactHome
  -> hg-web/src/index.html
  -> /page.js
  -> src/main.tsx
  -> components/app.tsx
```

### Repository browser

```text
directory-browser.tsx
  -> GET /api/repo/list?path=...
  -> ApiListDirectory
  -> GET hg-serve/file/tip/<path>?style=json

file or README selection
  -> GET /api/repo/file?path=...
  -> ApiGetFile
  -> GET hg-serve/raw-file/tip/<path>
  -> highlight.js, markdown_converter WASM, or inline static preview
```

Images, SVG, video, audio, and PDF files open in the forge preview modal.
Unknown binary files retain a download link. The raw file API supplies explicit
MIME types, `nosniff`, inline disposition for supported previews, and sandboxed
SVG responses.

### Commit graph

```text
graph.tsx
  -> GET /api/graph/<revision>?graphtop=...&style=json
  -> ApiGetGraph
  -> GET hg-serve/graph/<revision>?...
  -> React rows + custom pencil/panda canvas

graph row selection
  -> /changeset/<revision>
  -> GET /api/changeset/<revision>
  -> ApiGetChangeset
  -> GET hg-serve/json-rev/<revision>
  -> changeset metadata + rendered diff
```

### Mercurial clone, pull, and push

```text
Mercurial client
  -> GET or POST /repo
  -> StreamHgWireProtocol
  -> hg serve /?cmd=...
  -> binary-safe streamed response
```

## Route map

| Method | Route | Owner | Purpose |
| --- | --- | --- | --- |
| `GET` | `/` | `GetReactHome` | Application shell |
| `GET` | `/directories` | `GetReactHome` | Legacy application-shell route |
| `GET` | `/directory` | `GetReactHome` | Repository browser application route |
| `GET` | `/graph` | `GetReactHome` | Commit graph application route |
| `GET` | `/changeset/:changeset_id` | `GetReactHome` | Changeset application route |
| `GET` | `/api/repo/list` | `ApiListDirectory` | Directory listing JSON |
| `GET` | `/api/repo/file` | `ApiGetFile` | Raw tracked file |
| `GET` | `/api/repo/readme` | `ApiGetReadme` | Directory README content |
| `GET` | `/api/graph/:graph_id` | `ApiGetGraph` | Mercurial graph JSON |
| `GET` | `/api/changeset/:changeset_id` | `ApiGetChangeset` | Changeset metadata and diff JSON |
| `GET`, `POST` | `/repo` | `StreamHgWireProtocol` | Mercurial wire protocol |

## Frontend ownership

| File | Responsibility |
| --- | --- |
| `src/components/app.tsx` | Client-side routes, landing page, tabs, and history |
| `src/components/directory-browser.tsx` | Breadcrumbs, listings, prefetch, file modals, README rendering |
| `src/components/graph.tsx` | Graph fetching, pagination, canvas edges, panda nodes |
| `src/components/theme.tsx` | Stored light/dark preference and system-theme integration |
| `src/components/header.tsx` | Forge identity and theme control |
| `src/components/footer.tsx` | Shared footer |
| `src/index.css`, `src/base.css` | Custom visual language and layout |

Keep the custom assets and visual language in this package. New forge screens
should reuse the existing CSS variables, typography, textures, and components
instead of introducing a generic design system.

## Build and local run

Build the server and deployable bundle from the repository root:

```bash
bazel build //hg-web:hg_web_server
bazel build //hg-web:hg_web_server_bundle
bazel test //hg-web:tests
bazel test //markdown_converter/tests:markdown_to_html_test
bazel test //seobeo/tests:all
```

`//hg-web:tests` creates a temporary two-commit Mercurial repository and runs
the application in pinned Chromium through Playwright. It covers shell routes,
files and README rendering, graph and changeset navigation, browser history,
console and page errors, API validation, Mercurial wire protocol, Markdown
script escaping, static assets, and backend outages. The test invokes the
workspace Mercurial CLI with an isolated configuration and home directory.

Build and run the complete local stack with one Bazel command:

```bash
bazel run //hg-web:dev
```

This starts `hg serve` on `127.0.0.1:4444` and `hg_web_server` on port `6970`.
Stopping the Bazel target stops both child processes.

The bundle contains `hg_web_server` and `hg-web/src/`, including generated
`page.js`, markdown WASM, highlight.js, styles, and image assets.

## Deployment contract

The expected production layout is:

```text
nginx
  -> hg_web_server.service
     -> /opt/hg_web_server_bundle_active/hg_web_server
     -> working directory: /opt/hg_web_server_bundle_active
  -> hg serve service on 127.0.0.1:4444
```

`deploy.sh` builds an optimized bundle into a revisioned release directory,
atomically repoints `/opt/hg_web_server_bundle_active`, restarts
`hg_web_server.service`, and checks `http://127.0.0.1:6970/`. A failed restart
or health check restores the previous release and restarts it. The service,
release root, active path, health URL, user, and group can be overridden with
environment variables.

## Publishing

Push committed Mercurial changes to the configured server:

```bash
hg push
```

Then update and deploy on the server:

```bash
ssh -t [email protected] \
  'cd ~/zenbu && hg update default && bazel run //hg-web:deploy'
```

The SSH key may prompt for its passphrase. `//hg-web:deploy` builds the release
bundle through Bazel before running the atomic promotion, health check, and
rollback workflow.

## Forge capability tree

```text
Zenbu Forge
├── Repository
│   ├── File and directory browsing            available
│   ├── Syntax highlighting                    available
│   ├── README rendering                       available
│   ├── Commit graph                           available
│   ├── Changeset detail and diff              available
│   ├── Branches, bookmarks, and tags          planned
│   ├── File history and blame                 planned
│   └── Search                                 planned
├── Collaboration
│   ├── Authentication and authorization       planned
│   ├── Changeset review                       planned
│   ├── Issues                                 planned
│   └── Releases and artifacts                 planned
└── Automation
    ├── Mercurial incoming/changegroup hook    planned
    ├── Durable SQLite job queue               planned
    ├── Isolated Bazel runner                  planned
    ├── Live logs and job status API           planned
    ├── Forge status and log screens           planned
    ├── Artifact retention                     planned
    └── Atomic deploy, health check, rollback  available
```

## Automation data flow

The Actions-like subsystem should extend the existing server rather than
replace it:

```text
hg push
  -> Mercurial hook records repository + revision
  -> small enqueue command writes a SQLite job
  -> runner service claims one queued job
  -> isolated shared checkout updates to the exact revision
  -> Bazel build and test steps stream logs
  -> job result and artifacts are recorded
  -> hg-web status API exposes the result
  -> custom React screens render runs, steps, logs, and artifacts
  -> successful protected jobs may call atomic deployment
```

Start with one runner on the same host. Keep the hook fast, never execute build
steps inside the Mercurial request, and make the queue durable before adding
parallel or remote runners.

## Safe extension order

1. Add branches, bookmarks, tags, file history, blame, and search.
2. Add the hook, SQLite queue, single runner, and retained logs.
3. Add run/status/log pages using the existing custom UI.
4. Add authentication and authorization before accepting public pushes or
   user-defined automation.

## Invariants

- Keep public application code on Seobeo APIs.
- Decode and validate every path before forwarding it to Mercurial.
- Keep `/repo` binary-safe; bundle data can contain null bytes.
- Do not buffer large wire-protocol responses through the regular HTTP client.
- Keep backend route names synchronized with frontend fetch and navigation code.
- Preserve accurate status codes, content lengths, and content types.
- Keep secrets in service-owned environment files, not in source or job logs.