Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Build out the forge web UI

f09ef39efe44de693748053a7b596ef328fcf06b · Keenan Nemetz · 15d ago · parent 70b70264

Build out the forge web UI

Turn the web surface into a compact, server-rendered forge with HTMX as the
progressive dynamic layer.

- Split templates and assets into embedded files; add a tokenised dark theme,
  Lucide icons, and vendored HTMX.
- Add the repository Overview page and a separate chronological Activity feed
  with type filters and HTMX fragment updates.
- Finish the issue and change workflows: create pages, comments, close/reopen,
  labels and assignees, approval, discussion, line-anchored review comments,
  and HTMX list filters.
- Add syntax-highlighted blobs, contributor/statistics derivation, branch-aware
  code browsing, and a repository Runs list.
- Add a loopback-only development login for local write testing, with explicit
  configuration and listener checks.
- Extend tests for the new pages, fragments, labels, assignees, anchors, and
  development login.

server/auth_test.go +3−3

@@ -345,7 +345,7 @@ func TestContributorNamespaceAndRequestUseTheAccountName(t *testing.T) {
345 345 t.Fatalf("contributor issue: status=%d body=%s", resp.StatusCode, body)
346 346 }
347 347
348 resp, body = f.browserGet(t, "/r/"+f.repo+"/requests", cookie)
348 resp, body = f.browserGet(t, "/r/"+f.repo+"/requests/new", cookie)
349 349 if resp.StatusCode != http.StatusOK || !strings.Contains(body, headRef) || !strings.Contains(body, "Open change request") {
350 350 t.Fatalf("request form: status=%d body=%s", resp.StatusCode, body)
351 351 }
@@ -433,7 +433,7 @@ jobs:
433 433 }
434 434
435 435 contribSess, contribCookie := f.browserSession(t, contrib.ID)
436 resp, body := f.browserGet(t, "/r/"+f.repo, contribCookie)
436 resp, body := f.browserGet(t, "/r/"+f.repo+"/code", contribCookie)
437 437 if resp.StatusCode != http.StatusOK || !strings.Contains(body, "waiting for a maintainer") {
438 438 t.Fatalf("contributor branch status: status=%d body=%s", resp.StatusCode, body)
439 439 }
@@ -445,7 +445,7 @@ jobs:
445 445 }
446 446
447 447 ownerSess, ownerCookie := f.browserSession(t, f.ownerID)
448 resp, body = f.browserGet(t, "/r/"+f.repo, ownerCookie)
448 resp, body = f.browserGet(t, "/r/"+f.repo+"/code", ownerCookie)
449 449 if resp.StatusCode != http.StatusOK || !strings.Contains(body, "Allow CI for this tip") {
450 450 t.Fatalf("maintainer CI control missing: status=%d body=%s", resp.StatusCode, body)
451 451 }