~bigbes/sr-ht-compare

4f3961888c6889fc2ea19c9bcbb68359015d3475 — bigbes 9 days ago 19f0585
rename the service to diff.sr.ht

The instance-facing name changes and nothing else does: the config.ini section,
the origin, the apk package, the nginx block and the systemd unit are all
diff.sr.ht now, and the brand's red service label reads "diff" because chrome
derives it from the section name.

What deliberately stays is everything whose rename would cost more than it is
worth: the Go module path, the repository name, the comparesrht binary, and the
/~owner/repo/compare/base...head routes people have in their history. README
says so at the top so the split is not read as an oversight.
M .build.yml => .build.yml +1 -1
@@ 1,4 1,4 @@
# builds.sr.ht manifest for compare.sr.ht. One linear pipeline: install the
# builds.sr.ht manifest for diff.sr.ht. One linear pipeline: install the
# cache helper, assemble the shared SCSS, restore caches, package with abuild,
# publish the apk, save the caches.
#

M APKBUILD => APKBUILD +1 -1
@@ 21,7 21,7 @@
#
# A local `abuild` has no PKGVER and builds 0.0.0, which is what a package built
# by hand honestly is.
pkgname=compare.sr.ht
pkgname=diff.sr.ht
pkgver="${PKGVER:-0.0.0}"
pkgrel=0
pkgdesc="Stateless diff/compare viewer for a sourcehut instance"

M Makefile => Makefile +2 -2
@@ 1,11 1,11 @@
# compare.sr.ht — build scaffolding (git.sr.ht / sourcehut-dolt style).
# diff.sr.ht — build scaffolding (git.sr.ht / sourcehut-dolt style).
#
# Go binary + Go-template SSR live under cmd/ and web/. The CSS is built from
# the shared sourcehut scss partials with sassc; the frontend diff bundle is
# built once with esbuild and vendored into web/static/. Node is a build-time
# dependency only.

SERVICE=compare.sr.ht
SERVICE=diff.sr.ht
BIN=comparesrht

PREFIX?=/usr/local

M README.md => README.md +27 -18
@@ 1,18 1,27 @@
# compare.sr.ht
# diff.sr.ht

A git compare/diff viewer for a self-hosted [SourceHut] instance. It compares
two refs (branches, tags, or commits) of any git.sr.ht repository — `base...head`
or `base..head` — and renders the diff with [`@pierre/diffs`] (Shiki-based
syntax-highlighted diffs) and a [`@pierre/trees`] file tree, plus a
single-commit view. Runs at `https://compare.srht.bigb.es`.
single-commit view. Runs at `https://diff.srht.bigb.es`.

[SourceHut]: https://sourcehut.org
[`@pierre/diffs`]: https://www.npmjs.com/package/@pierre/diffs
[`@pierre/trees`]: https://www.npmjs.com/package/@pierre/trees

> **The service is `diff.sr.ht`; the repository is still `sr-ht-compare`.** The
> service was called `compare.sr.ht` until it was renamed, and what the rename
> moved is everything the instance sees: the `config.ini` section, the origin,
> the apk package, the container. What it deliberately did not move is anything
> with a cost and no benefit — the Go module path
> (`sourcecraft.dev/bigbes/sr-ht-compare`), the repository name, the binary
> (`comparesrht`) and the `/~owner/repo/compare/base...head` routes, which are
> the URLs people have in their history.

## Architecture

compare.sr.ht is a **stateless Go daemon**: it owns no database and no object
diff.sr.ht is a **stateless Go daemon**: it owns no database and no object
storage, so it needs neither Postgres nor Redis. It integrates into SourceHut
purely through configuration — no upstream sources are modified (the
[sourcehut-custom-service integration model]). Repository data is read directly


@@ 91,7 100,7 @@ make bundle  # rebuild the vendored frontend bundle (esbuild)

## Configuration

See `config.example.ini`. On a real instance, append the `[compare.sr.ht]`
See `config.example.ini`. On a real instance, append the `[diff.sr.ht]`
section to the shared `/etc/sr.ht/config.ini`; the service reads the instance's
existing shared keys (`[sr.ht]`, `[webhooks]`, `[meta.sr.ht]`, `[git.sr.ht]`) in
place. core-go's `LoadConfig()` searches, in order: `./config.ini`,


@@ 100,7 109,7 @@ place. core-go's `LoadConfig()` searches, in order: `./config.ini`,
At startup the daemon validates that all required keys are present — the
`[sr.ht] network-key`, `[webhooks] private-key`, `[git.sr.ht] repos`, a
git.sr.ht API origin candidate (`api-internal-origin` / `internal-origin` /
`api-origin` / `origin`), `[meta.sr.ht] origin`, and `[compare.sr.ht] origin` —
`api-origin` / `origin`), `[meta.sr.ht] origin`, and `[diff.sr.ht] origin` —
and exits with a single clear message listing anything missing.

## Development


@@ 120,7 129,7 @@ dev-stub GraphQL API (no real instance required).
   [webhooks]
   private-key=<base64 of 32 random bytes>

   [compare.sr.ht]
   [diff.sr.ht]
   origin=http://localhost:5090

   [meta.sr.ht]


@@ 216,47 225,47 @@ shows "Logged in as bigbes" with your repository list (from the stub's

## Deployment

compare.sr.ht deploys like any other SourceHut web service. On the instance:
diff.sr.ht deploys like any other SourceHut web service. On the instance:

1. **Install** the binary and static assets:

   ```sh
   make install PREFIX=/usr/local
   # → /usr/local/bin/comparesrht
   # → /usr/share/sourcehut/compare.sr.ht/static/{bundle.<hash>.js,main.min.<hash>.css}
   # → /usr/share/sourcehut/diff.sr.ht/static/{bundle.<hash>.js,main.min.<hash>.css}
   ```

2. **Config.** Append the `[compare.sr.ht]` section (`origin=` and
   `static-dir=`) to the shared `/etc/sr.ht/config.ini`. To make compare.sr.ht
2. **Config.** Append the `[diff.sr.ht]` section (`origin=` and
   `static-dir=`) to the shared `/etc/sr.ht/config.ini`. To make diff.sr.ht
   appear in the shared nav/service-switcher of the *other* services, that same
   `[compare.sr.ht] origin=` line must be visible to their configs too (on a
   `[diff.sr.ht] origin=` line must be visible to their configs too (on a
   single shared `config.ini` this is automatic) — then **restart those
   services** (git.sr.ht, meta.sr.ht, …) so they pick up the new switcher entry.

3. **DNS.** Point `compare.srht.bigb.es` at the instance. It must be a subdomain
3. **DNS.** Point `diff.srht.bigb.es` at the instance. It must be a subdomain
   of the shared cookie domain (`*.srht.bigb.es`) so the unified-login cookie is
   sent to us.

4. **internal-ipnet.** This host must fall inside the git.sr.ht API's
   `[sr.ht] internal-ipnet` CIDR, or the internal-auth GraphQL calls
   (including anonymous `AUTH_ANON_INTERNAL`) are rejected. If compare.sr.ht
   (including anonymous `AUTH_ANON_INTERNAL`) are rejected. If diff.sr.ht
   runs on the same box as git.sr.ht, the default loopback/private ranges
   already cover it.

5. **nginx.** Install `contrib/compare.sr.ht.conf` alongside the other
5. **nginx.** Install `contrib/diff.sr.ht.conf` alongside the other
   `*.sr.ht.conf` files (adjust `server_name`), provide the TLS cert include it
   references (`compare-ssl.conf`), and reload nginx. It is a plain
   references (`diff-ssl.conf`), and reload nginx. It is a plain
   `proxy_pass http://127.0.0.1:5090`; static assets are served by the app from
   its embedded FS with immutable cache headers.

6. **systemd.** Install `contrib/compare-srht.service`, then
   `systemctl enable --now compare-srht`. The unit runs as **`User=git`** so it
6. **systemd.** Install `contrib/diff-srht.service`, then
   `systemctl enable --now diff-srht`. The unit runs as **`User=git`** so it
   has read access to the bare repositories under `/var/lib/git`
   (`ReadOnlyPaths=/var/lib/git`, `ProtectSystem=strict` — the service only ever
   reads). It stops with `KillSignal=SIGINT` because core-go's `server.Run`
   performs its warm shutdown on SIGINT, not the systemd default SIGTERM.

7. **Verify.** `GET https://compare.srht.bigb.es/healthz` → `ok`. Logged in,
7. **Verify.** `GET https://diff.srht.bigb.es/healthz` → `ok`. Logged in,
   the landing page lists your repos and a PUBLIC repo's `main...branch` compare
   renders with syntax highlighting, the tree sidebar, and the split/unified
   toggle; a PRIVATE repo is a 404 to anonymous viewers and 200 to its owner.

M authz/authz.go => authz/authz.go +1 -1
@@ 12,7 12,7 @@ import (
	"sourcecraft.dev/bigbes/sr-ht-compare/core"
)

// RepoInfo is the subset of git.sr.ht repository metadata compare.sr.ht needs
// RepoInfo is the subset of git.sr.ht repository metadata diff.sr.ht needs
// to render a page. It carries no ownership or ACL data: the mere fact that the
// authorizer returned a RepoInfo means the viewer is allowed to see the repo.
type RepoInfo struct {

M authz/authz_test.go => authz/authz_test.go +2 -2
@@ 55,10 55,10 @@ func authNameFromRequest(t *testing.T, r *http.Request) string {
// url — the ephemeral httptest server of the calling test — over the synthetic
// instance whose keyset TestMain installed.
func ctxFor(url string) context.Context {
	conf := ecoretest.Config("compare.sr.ht",
	conf := ecoretest.Config("diff.sr.ht",
		ecoretest.Set("git.sr.ht", "api-origin", url),
	)
	return config.Context(context.Background(), conf, "compare.sr.ht")
	return config.Context(context.Background(), conf, "diff.sr.ht")
}

func TestRepo_Found(t *testing.T) {

M authz/doc.go => authz/doc.go +2 -2
@@ 1,4 1,4 @@
// Package authz answers one question for compare.sr.ht: what may a given viewer
// Package authz answers one question for diff.sr.ht: what may a given viewer
// see. Who the viewer is, is not this package's question any more — the
// unified-login cookie is decoded by sr-ht-ecore/login, which every custom
// service on the instance shares, and a handler reads the answer with


@@ 7,7 7,7 @@
// is one package now.
//
// Authorization is delegated entirely to git.sr.ht over its internal GraphQL
// API: compare.sr.ht owns no user or repository data of its own, so there is no
// API: diff.sr.ht owns no user or repository data of its own, so there is no
// database. GQLAuthorizer issues each query as the viewing user via
// core-go's client.Do (Authorization: Internal <fernet(...)>), letting
// git.sr.ht's own loader apply visibility rules — an anonymous or unauthorized

M cmd/comparesrht/main.go => cmd/comparesrht/main.go +6 -6
@@ 1,11 1,11 @@
// Command comparesrht is the compare.sr.ht daemon: a stateless HTTP service
// Command comparesrht is the diff.sr.ht daemon: a stateless HTTP service
// that renders git ref-to-ref diffs and single-commit views for a self-hosted
// SourceHut instance.
//
// It reuses core-go's server.New assembly so it shares the standard SourceHut
// daemon lifecycle (crypto initialization, -b/-m/-p flags, warm shutdown on
// SIGINT) with the rest of the fleet, but it deliberately does NOT call
// WithDefaultMiddleware: compare.sr.ht owns no Postgres or Redis and must serve
// WithDefaultMiddleware: diff.sr.ht owns no Postgres or Redis and must serve
// anonymous viewers, so it installs its own lightweight middleware group on the
// anonymous router instead (see the web package documentation for the exact
// chain).


@@ 47,7 47,7 @@ import (
)

const (
	service     = "compare.sr.ht"
	service     = "diff.sr.ht"
	defaultBind = "127.0.0.1:5090"

	// authzTTL is how long the GraphQL authorizer memoizes positive and


@@ 73,7 73,7 @@ const (
// the real flag parse and does not run until after config validation, and a
// daemon that only became verbose once it had finished starting would be silent
// for exactly the part of its life an operator passes -d to watch. The two
// knobs that work here are -d and $LOG_LEVEL; a [compare.sr.ht] log-level key
// knobs that work here are -d and $LOG_LEVEL; a [diff.sr.ht] log-level key
// would be read too late to matter and is not supported.
func initLogging() {
	opts := logging.Defaults(nil, "")


@@ 164,7 164,7 @@ func main() {
	})

	reposRoot, _ := conf.Get("git.sr.ht", "repos")
	slog.Info("compare.sr.ht starting",
	slog.Info("diff.sr.ht starting",
		"bind", resolveBind(os.Args[1:]),
		"repos", reposRoot,
		"git.sr.ht-api", apiOrigin,


@@ 175,7 175,7 @@ func main() {
	srv.Run()
}

// validateConfig verifies every configuration key compare.sr.ht needs before it
// validateConfig verifies every configuration key diff.sr.ht needs before it
// can serve or authorize a request. It reports all missing keys at once, in a
// single record followed by a single exit, so operators fix the config in one
// pass instead of discovering each gap on a separate restart. It returns the

M cmd/comparesrht/main_test.go => cmd/comparesrht/main_test.go +1 -1
@@ 56,7 56,7 @@ environment=development
[webhooks]
private-key=%s

[compare.sr.ht]
[diff.sr.ht]
origin=%s

[meta.sr.ht]

M config.example.ini => config.example.ini +10 -10
@@ 1,23 1,23 @@
# Example configuration for compare.sr.ht.
# Example configuration for diff.sr.ht.
#
# compare.sr.ht is a stateless service: it owns no database and no object
# storage. Apart from its own [compare.sr.ht] section it only *reads* keys that
# diff.sr.ht is a stateless service: it owns no database and no object
# storage. Apart from its own [diff.sr.ht] section it only *reads* keys that
# already exist in the shared sourcehut config.ini of the instance. On a
# deployed instance you normally append the [compare.sr.ht] section below to the
# deployed instance you normally append the [diff.sr.ht] section below to the
# instance's existing /etc/sr.ht/config.ini rather than shipping a standalone
# file. core-go's LoadConfig() searches: ./config.ini, ../config.ini,
# /etc/sr.ht/config.ini, /etc/sr.ht/*.ini.

[compare.sr.ht]
[diff.sr.ht]
#
# Public origin of this service. Used to build self URLs and the logout
# return_to. Cookie auth requires this to be a subdomain of the shared cookie
# domain (*.srht.bigb.es).
origin=https://compare.srht.bigb.es
origin=https://diff.srht.bigb.es
#
# Directory the built static assets (bundle.<hash>.js, main.min.<hash>.css,
# logo.svg) are installed to; globbed at startup to resolve the hashed filenames.
static-dir=/usr/share/sourcehut/compare.sr.ht/static
static-dir=/usr/share/sourcehut/diff.sr.ht/static
#
# Address the HTTP server binds to. Optional; defaults to 127.0.0.1:5090.
#bind-address=127.0.0.1:5090


@@ 34,7 34,7 @@ static-dir=/usr/share/sourcehut/compare.sr.ht/static
# and (b) mint the Internal auth token for git.sr.ht GraphQL. REQUIRED by
# crypto.InitCrypto.
network-key=
# site-name: shown as the nav brand ("<site-name> compare").
# site-name: shown as the nav brand ("<site-name> diff").
site-name=sourcehut
# environment: "production" hides the dev banner; anything else shows it.
environment=production


@@ 43,7 43,7 @@ environment=production
internal-ipnet=10.0.0.0/8,127.0.0.0/8

[webhooks]
# private-key: base64 Ed25519 seed. compare.sr.ht emits no webhooks, but
# private-key: base64 Ed25519 seed. diff.sr.ht emits no webhooks, but
# crypto.InitCrypto fatally requires this key to be present.
private-key=



@@ 59,6 59,6 @@ origin=https://git.srht.bigb.es
# GetAPI falls back through api-internal-origin/internal-origin/api-origin/
# origin; at least one must be set or startup fails.
api-origin=https://git.srht.bigb.es
# repos: on-disk root of bare repositories. compare.sr.ht reads
# repos: on-disk root of bare repositories. diff.sr.ht reads
# {repos}/~{owner}/{name} directly (read-only) after authorization passes.
repos=/var/lib/git

M contrib/dev-stub/main.go => contrib/dev-stub/main.go +3 -3
@@ 1,5 1,5 @@
// Command dev-stub is a throwaway fake git.sr.ht GraphQL API for local
// development of compare.sr.ht. It answers the two queries compare.sr.ht's
// development of diff.sr.ht. It answers the two queries diff.sr.ht's
// authorizer issues (a single `user{repository}` lookup and the `me`
// repository list) with fixed PUBLIC data, so you can drive the UI without a
// real SourceHut instance or its internal-auth machinery.


@@ 12,7 12,7 @@
//
//	go run ./contrib/dev-stub -addr 127.0.0.1:5101
//
// Then point compare.sr.ht's config at it and start the daemon:
// Then point diff.sr.ht's config at it and start the daemon:
//
//	[git.sr.ht]
//	api-origin=http://127.0.0.1:5101   # dev-stub serves POST /query here


@@ 36,7 36,7 @@ import (
	"strings"
)

// repoNode is the repository shape compare.sr.ht's authorizer decodes for both
// repoNode is the repository shape diff.sr.ht's authorizer decodes for both
// the single-repo lookup and the me.repositories list.
type repoNode struct {
	ID          int    `json:"id"`

R contrib/compare-srht.service => contrib/diff-srht.service +2 -2
@@ 1,11 1,11 @@
[Unit]
Description=compare.sr.ht git diff/compare viewer
Description=diff.sr.ht git diff/compare viewer
After=network.target

[Service]
Type=simple
# Runs as the git user so it has read access to the bare repositories under
# /var/lib/git. compare.sr.ht is stateless — no database or Redis dependency.
# /var/lib/git. diff.sr.ht is stateless — no database or Redis dependency.
User=git
Restart=always
ExecStart=/usr/local/bin/comparesrht -b 127.0.0.1:5090

R contrib/compare.sr.ht.conf => contrib/diff.sr.ht.conf +6 -6
@@ 1,22 1,22 @@
# nginx server block for compare.sr.ht (sr.ht-nginx style).
# nginx server block for diff.sr.ht (sr.ht-nginx style).
#
# Drop into the sr.ht-nginx config alongside the other *.sr.ht.conf files and
# adjust server_name to the instance domain (e.g. compare.srht.bigb.es).
# compare.sr.ht serves its own static assets from the embedded FS with
# adjust server_name to the instance domain (e.g. diff.srht.bigb.es).
# diff.sr.ht serves its own static assets from the embedded FS with
# immutable cache headers, so everything is proxied to the app; there is no
# GraphQL /query endpoint of its own.

server {
	include sourcehut.conf;
	include port80.conf;
	server_name compare.sr.ht;
	server_name diff.sr.ht;
}

server {
	include sourcehut.conf;
	include port443.conf;
	include compare-ssl.conf;
	server_name compare.sr.ht;
	include diff-ssl.conf;
	server_name diff.sr.ht;

	location / {
		proxy_pass http://127.0.0.1:5090;

M core/errors.go => core/errors.go +1 -1
@@ 1,4 1,4 @@
// Package core holds the pure domain logic of compare.sr.ht: input
// Package core holds the pure domain logic of diff.sr.ht: input
// validation (owner/repo/ref names), the compare-spec grammar, and the
// sentinel errors shared across the service. It has no external dependencies
// and never touches the network or the filesystem, so it is cheap to test.

M docs/ci.md => docs/ci.md +1 -1
@@ 393,7 393,7 @@ parallel — the copying would start beside the build it is supposed to follow.
`make install-files` still runs
`install -Dm644 -t $(DESTDIR)$(STATICDIR) web/static/*`, which stages
`bundle.<hash>.js` and `main.min.<hash>.css` into
`/usr/share/sourcehut/compare.sr.ht/static`. The binary already **embeds** both
`/usr/share/sourcehut/diff.sr.ht/static`. The binary already **embeds** both
(`//go:embed static` in `web/templates.go:25`) and serves them from there, so
those copies are dead weight in the apk and a second, divergable source of truth
for the same bytes. The bench sibling installs no static assets at all for this

M docs/inline-comments.md => docs/inline-comments.md +7 -7
@@ 6,7 6,7 @@ Author: bigbes. Last updated: 2026-07-18.
## 1. Motivation

The reference diff viewers (GitHub, the `diffshub` design we mirror) let a viewer
attach a comment to a specific line of a diff and reply in a thread. compare.sr.ht
attach a comment to a specific line of a diff and reply in a thread. diff.sr.ht
renders commit and compare (`base...head`) diffs but has no way to discuss them
in place. SourceHut's native review flow is patch-over-mailing-list
(`lists.sr.ht`); inline comments are a **net-new capability** for the instance,


@@ 17,9 17,9 @@ compare pages.

## 2. Key constraint: the service is stateless *by choice*

compare.sr.ht owns no persistence today. `cmd/comparesrht/main.go` spells it out:
diff.sr.ht owns no persistence today. `cmd/comparesrht/main.go` spells it out:

> it deliberately does NOT call `WithDefaultMiddleware`: compare.sr.ht owns no
> it deliberately does NOT call `WithDefaultMiddleware`: diff.sr.ht owns no
> Postgres or Redis and must serve anonymous viewers

Crucially this is a **choice, not a missing capability**. The shared


@@ 31,7 31,7 @@ and it is already in our build (as an indirect dependency: `github.com/lib/pq`):
  (`WithTx`, `WithReadOnlyTx`), and a squirrel-based query builder with a
  `Model`/`Scan` layer (`sq.go`, `ql.go`).
- `sr-ht-core/server` `WithDefaultMiddleware()` opens the pool from a
  `[compare.sr.ht]::connection-string` key via `sql.Open("postgres", …)`.
  `[diff.sr.ht]::connection-string` key via `sql.Open("postgres", …)`.

So the storage foundation is sitting unused. Adding comments means **turning that
path back on** for the comment routes while keeping anonymous, DB-free browsing


@@ 44,7 44,7 @@ Adding a DB makes the service no longer stateless. Downstream updates required:

- `README.md` and the `server.go` package doc ("owns no state of its own").
- The `main.go` "no database" comment and middleware group.
- `contrib/compare-srht.service` — add a Postgres ordering dependency.
- `contrib/diff-srht.service` — add a Postgres ordering dependency.
- Ops: the service now has backup/restore concerns and a schema to migrate.

## 3. Anchoring model (the crux)


@@ 191,7 191,7 @@ sanitized HTML). Never inject raw comment HTML.
New key:

```ini
[compare.sr.ht]
[diff.sr.ht]
connection-string=postgres://compare:secret@localhost/compare?sslmode=disable
```



@@ 201,7 201,7 @@ browsing intact — the middleware only injects the pool; only comment handlers
touch it):

```go
db, err := sql.Open("postgres", connString)   // from [compare.sr.ht] connection-string
db, err := sql.Open("postgres", connString)   // from [diff.sr.ht] connection-string
// …
r.Use(database.Middleware(db))                 // added alongside config + authz middleware
```

M frontend/package.json => frontend/package.json +1 -1
@@ 2,7 2,7 @@
  "name": "compare-frontend",
  "version": "0.0.0",
  "private": true,
  "description": "Build-time diff/tree bundle for compare.sr.ht (vendored into web/static/bundle.js).",
  "description": "Build-time diff/tree bundle for diff.sr.ht (vendored into web/static/bundle.js).",
  "type": "module",
  "scripts": {
    "build": "esbuild src/app.ts --bundle --minify --format=esm --outfile=../web/static/bundle.js"

M frontend/src/app.ts => frontend/src/app.ts +1 -1
@@ 1,5 1,5 @@
/*
 * compare.sr.ht frontend bundle.
 * diff.sr.ht frontend bundle.
 *
 * Consumes the SSR DOM contract:
 *   <script id="compare-data" type="application/json">{...}</script>

M gitx/gitx.go => gitx/gitx.go +1 -1
@@ 1,4 1,4 @@
// Package gitx is the git access layer of compare.sr.ht. It opens bare
// Package gitx is the git access layer of diff.sr.ht. It opens bare
// repositories on disk with go-git and answers ref, log, and diff queries
// entirely in-process (no git binary is executed at runtime). Every operation
// is bounded by a context timeout, generated patch text is capped in size (so

M scss/main.scss => scss/main.scss +1 -1
@@ 1,4 1,4 @@
// compare.sr.ht stylesheet.
// diff.sr.ht stylesheet.
//
// Mirrors the SourceHut service pattern (see paste.sr.ht/scss/main.scss): pull
// in the shared `base` partial (Bootstrap 4 + the SourceHut chrome: contrast,

M web/router.go => web/router.go +1 -1
@@ 11,7 11,7 @@ import (
	"sourcecraft.dev/bigbes/sr-ht-ecore/middleware"
)

// Register mounts every compare.sr.ht route onto r, inside a group carrying the
// Register mounts every diff.sr.ht route onto r, inside a group carrying the
// three middlewares that need this Server. The chain documented on the package
// (config + authz at a minimum) is still the caller's, and stays outside this
// group.

M web/server.go => web/server.go +4 -4
@@ 1,4 1,4 @@
// Package web is the HTTP layer of compare.sr.ht. It renders the repository
// Package web is the HTTP layer of diff.sr.ht. It renders the repository
// landing, compare (base...head) and single-commit pages server-side, and
// embeds a compact JSON payload plus the vendored esbuild bundle so the browser
// renders the diff with @pierre/diffs and @pierre/trees.


@@ 38,7 38,7 @@
//	chi middleware.RealIP
//	chimw.RequestLogger(...)     // the request line, as a slog record
//	chi middleware.Recoverer
//	config.Middleware(conf, "compare.sr.ht")   // required: authz + gitx read it
//	config.Middleware(conf, "diff.sr.ht")   // required: authz + gitx read it
//	login.Optional()             // required: never 401s; sets the viewer
//
// login.Optional and not login.Required: every page here is either public or a


@@ 71,7 71,7 @@ import (
// be spelled the same here, in the config file and in the middleware the cmd
// layer installs — a service that spelled it two ways would appear in the
// instance's navigation and fail to recognise itself in it.
const configSection = "compare.sr.ht"
const configSection = "diff.sr.ht"

// bundleAsset is the key compare's layout reads its front-end bundle's href
// under, in chrome.Service.Assets. It is spelled once here and once in the


@@ 105,7 105,7 @@ type Server struct {
}

// New assembles a Server from the shared SourceHut config. It reads
// [git.sr.ht] repos, [meta.sr.ht] origin and [compare.sr.ht] origin (all
// [git.sr.ht] repos, [meta.sr.ht] origin and [diff.sr.ht] origin (all
// required), hands the whole file to chrome.NewService — the switcher is a
// question about every [*.sr.ht] section the instance defines, not about our own
// keys — resolves the hashed stylesheet and bundle through ecore's assets, and

M web/web_test.go => web/web_test.go +2 -2
@@ 153,7 153,7 @@ func testServer(t *testing.T, root string, az authz.Authorizer) http.Handler {
	require.NoError(t, err, "New")

	r := chi.NewRouter()
	r.Use(config.Middleware(conf, "compare.sr.ht"))
	r.Use(config.Middleware(conf, "diff.sr.ht"))
	r.Use(login.Optional())
	srv.Register(r)
	return r


@@ 334,7 334,7 @@ func TestChromeIsRendered(t *testing.T) {
	h := testServer(t, root, demoAuthorizer())

	anon := get(t, h, "/", "").Body.String()
	assert.Contains(t, anon, `<span class="text-danger">compare</span>`,
	assert.Contains(t, anon, `<span class="text-danger">diff</span>`,
		"the brand's service label is missing")
	assert.Contains(t, anon, "Log in", "an anonymous viewer must be offered the login")
	// The environment is "development" in the test config, so the banner shows.