vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Build a landing page and simplify the forge chrome
074ae1605e177e99533af02d8c69ccd1a6a947bb · Keenan Nemetz · 14d ago · parent 62576fa8
Build a landing page and simplify the forge chrome The instance root is now a landing page rather than the full activity feed: forge identity, project cards with top language, last commit, issue/change counts and latest release, a short activity rail, and a first-run empty state. The full feed moves to /activity with type filters and pagination. Feeds now cover issues, changes, comments, and releases only; commits and CI runs already have their own pages. Configuration gains instance_name and instance_tagline. Navigation: the crowded global bar becomes Runners, Activity, search, and an account menu holding Admin, Status, and Log out. Repository pages show the name and description above the tabs at the home-page header scale, while tab-page titles stay small. Top-level pages keep the larger header. Drop the workflow run chip, assigned-issues strip, repository size stat, and footer Status link.
server/ui_test.go +20−16
| @@ -58,15 +58,17 @@ func TestRepoTabShowsNameAndVisibility(t *testing.T) { | |||
| 58 | 58 | _, cookie := f.browserSession(t, f.ownerID) | |
| 59 | 59 | resp, body := f.browserGet(t, "/r/"+f.repo, cookie) | |
| 60 | 60 | if resp.StatusCode != 200 || !strings.Contains(body, `class="repotab active"`) || | |
| 61 | !strings.Contains(body, `title="Private repository"`) || !strings.Contains(body, ">demo</a>") { | ||
| 62 | t.Fatalf("private repo tab = %d:\n%s", resp.StatusCode, body) | ||
| 61 | !strings.Contains(body, `title="Private repository"`) || !strings.Contains(body, "demo</div>") { | ||
| 62 | t.Fatalf("private repo header = %d:\n%s", resp.StatusCode, body) | ||
| 63 | } | ||
| 64 | if strings.Index(body, `class="repo-name"`) > strings.Index(body, `class="tabs"`) { | ||
| 65 | t.Fatalf("repository title is not above the tabs:\n%s", body) | ||
| 63 | 66 | } | |
| 64 | 67 | ||
| 65 | 68 | f.publish(f.repo) | |
| 66 | 69 | _, body = f.get("/r/" + f.repo + "/code") | |
| 67 | if !strings.Contains(body, `title="Public repository"`) || | ||
| 68 | !strings.Contains(body, `aria-label="demo (public repository)"`) { | ||
| 69 | t.Fatalf("public repo tab does not show the globe state:\n%s", body) | ||
| 70 | if !strings.Contains(body, `title="Public repository"`) || !strings.Contains(body, ">Overview</a>") { | ||
| 71 | t.Fatalf("public repo header does not show the globe state or Overview tab:\n%s", body) | ||
| 70 | 72 | } | |
| 71 | 73 | } | |
| 72 | 74 | ||
| @@ -121,16 +123,12 @@ func TestOverviewAndActivityPages(t *testing.T) { | |||
| 121 | 123 | t.Fatalf("overview page does not show %q:\n%s", want, body) | |
| 122 | 124 | } | |
| 123 | 125 | } | |
| 124 | // The activity tab is a separate page, and its commit filter is a fragment. | ||
| 126 | // The activity tab is a separate page. Commits are not feed events: they | ||
| 127 | // belong to the overview and the code browser. | ||
| 125 | 128 | code, body = f.get("/r/" + f.repo + "/activity") | |
| 126 | if code != 200 || !strings.Contains(body, "Activity") || !strings.Contains(body, "initial") { | ||
| 129 | if code != 200 || !strings.Contains(body, "Activity") || strings.Contains(body, "committed") { | ||
| 127 | 130 | t.Fatalf("activity page = %d:\n%s", code, body) | |
| 128 | 131 | } | |
| 129 | resp, fragment := f.htmxGet(t, "/r/"+f.repo+"/activity?kind=commits", "") | ||
| 130 | if resp.StatusCode != 200 || !strings.Contains(fragment, `id="activity-feed"`) || | ||
| 131 | strings.Contains(fragment, "<!doctype html>") || !strings.Contains(fragment, "initial") { | ||
| 132 | t.Fatalf("commit activity fragment = %d:\n%s", resp.StatusCode, fragment) | ||
| 133 | } | ||
| 134 | 132 | // Code browsing moved to its own tab, and that page still lists the tree. | |
| 135 | 133 | if code, codePage := f.get("/r/" + f.repo + "/code"); code != 200 || !strings.Contains(codePage, "main.go") { | |
| 136 | 134 | t.Fatalf("code page = %d:\n%s", code, codePage) | |
| @@ -235,7 +233,10 @@ func TestActivityFeedIncludesIssuesAndFilters(t *testing.T) { | |||
| 235 | 233 | f := newFixture(t) | |
| 236 | 234 | f.publish(f.repo) | |
| 237 | 235 | work := workRepo(t, map[string]string{"README.md": "# activity\n"}) | |
| 238 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main"); err != nil { | ||
| 236 | if _, errs, err := runGit(t, work, "tag", "-a", "v1.0.0", "-m", "release notes"); err != nil { | ||
| 237 | t.Fatalf("tag: %s", errs) | ||
| 238 | } | ||
| 239 | if _, errs, err := f.git(work, "push", f.sshURL(f.repo), "main:refs/heads/main", "refs/tags/v1.0.0"); err != nil { | ||
| 239 | 240 | t.Fatalf("main push: %s", errs) | |
| 240 | 241 | } | |
| 241 | 242 | issues := f.issuesRepo(t, issueFiles("01M5JR2JK29KKMAA1T9MDNTPE1", "Activity issue", "body\n")) | |
| @@ -244,17 +245,20 @@ func TestActivityFeedIncludesIssuesAndFilters(t *testing.T) { | |||
| 244 | 245 | } | |
| 245 | 246 | ||
| 246 | 247 | _, body := f.get("/r/" + f.repo + "/activity") | |
| 247 | for _, want := range []string{"Activity issue", "opened issue", "committed"} { | ||
| 248 | for _, want := range []string{"Activity issue", "opened issue", "v1.0.0", "released"} { | ||
| 248 | 249 | if !strings.Contains(body, want) { | |
| 249 | 250 | t.Fatalf("activity feed does not show %q:\n%s", want, body) | |
| 250 | 251 | } | |
| 251 | 252 | } | |
| 253 | if strings.Contains(body, "committed") { | ||
| 254 | t.Fatalf("activity feed still shows commits:\n%s", body) | ||
| 255 | } | ||
| 252 | 256 | _, body = f.get("/r/" + f.repo + "/activity?kind=issues") | |
| 253 | 257 | if !strings.Contains(body, "Activity issue") { | |
| 254 | 258 | t.Fatalf("issue filter dropped the issue:\n%s", body) | |
| 255 | 259 | } | |
| 256 | if strings.Contains(body, "committed") { | ||
| 257 | t.Fatalf("issue filter kept a commit:\n%s", body) | ||
| 260 | if strings.Contains(body, "v1.0.0") { | ||
| 261 | t.Fatalf("issue filter kept a release:\n%s", body) | ||
| 258 | 262 | } | |
| 259 | 263 | resp, fragment := f.htmxGet(t, "/r/"+f.repo+"/activity?kind=issues", "") | |
| 260 | 264 | if resp.StatusCode != 200 || !strings.Contains(fragment, `id="activity-feed"`) || | |