Mercurial
view hg-web/README.md @ 256:30c2196d03d4
[site] Integrate Zenbu themes and components
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 16:49:01 -0700 |
| parents | c5129452493e |
| children |
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`. The `hg serve` process comes from `//third_party/mercurial:runtime` and points at the current workspace. Stopping the Bazel target stops both child processes. The bundle contains `hg_web_server`, `hg-web/run_hg_web`, a pinned pure-Python Mercurial runtime, 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/active/hg-web/run_hg_web -> bundled Mercurial 7.2.3 serves /opt/hg_web_server/repository on 127.0.0.1:4444 -> hg_web_server serves the forge on 127.0.0.1:6970 ``` `deploy.sh` follows the same new/active bundle swap as the personal site. It builds the optimized bundle and maintains a canonical repository outside the release directories. The first deploy creates it with a locking-aware local clone. Later deploys create a Mercurial bundle from the workspace and pull that bundle into the persistent repository, preserving any commits pushed directly to production. The script verifies the repository, installs the systemd unit, swaps the active directory, restarts the combined service, and checks `http://127.0.0.1:6970/`. A failed restart restores the previous active directory. There is no separate production `hg serve` service and no dependency on the host's `hg` command. The pinned Mercurial wheel requires CPython 3.11. The systemd unit uses `KillMode=control-group`, so both the Mercurial and C child processes stop together. On its first run, deploy also installs `/etc/nginx/sites-available/hg-web` when Nginx uses Debian-style `sites-available`/`sites-enabled` directories. Existing site files are left untouched. ```bash NGINX_SERVER_NAME=zenbu.babocoder.com bazel run //hg-web:deploy ``` The checked-in template listens on origin HTTP port 80, which works when TLS is terminated upstream. For direct origin TLS, provide a complete certificate-aware template: ```bash NGINX_SITE_TEMPLATE=/etc/nginx/templates/hg-web-tls.conf \ bazel run //hg-web:deploy ``` Set `MANAGE_NGINX=false` to leave Nginx untouched. `NGINX_SITE_NAME`, `NGINX_SITE_AVAILABLE`, and `NGINX_SITE_ENABLED` can override the destination names and paths. `DEPLOY_ROOT` controls the validated deployment root; `REPOSITORY_PATH` can override the persistent canonical repository within it. Pushes are disabled by default. `HG_ALLOW_PUSH=true` enables unauthenticated pushes in the internal `hg serve`; only use it when `/repo` is protected by Nginx authentication or another trusted access layer. ## 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.