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.
docs/ui-plan.md +172
| @@ -0,0 +1,172 @@ | |||
| 1 | # UI plan | ||
| 2 | |||
| 3 | Working notes for Vole's web surface. Decisions here are the brief; change them | ||
| 4 | deliberately rather than by drift. | ||
| 5 | |||
| 6 | ## Direction | ||
| 7 | |||
| 8 | - **Compact, not sparse.** Take Linear's information density and restraint. | ||
| 9 | Avoid GitHub's whitespace-heavy chrome; avoid Sourcehut's bareness. A row of | ||
| 10 | data should be readable at a glance, and a page should fit on one screen when | ||
| 11 | it can. | ||
| 12 | - **Dark only for now.** Every colour comes from a custom property on `:root`. | ||
| 13 | A light theme is a second block of variables, not a rewrite. | ||
| 14 | - **Server rendered.** Go `html/template` produces the page. HTMX is the default | ||
| 15 | dynamic layer, vendored into the binary; JavaScript remains for passkeys, | ||
| 16 | clipboard copy, and rich widgets such as the label/assignee token input. | ||
| 17 | No SPA, no client-side router, no npm build. | ||
| 18 | - **Progressive enhancement.** Forms keep a normal `method`/`action` fallback. | ||
| 19 | Handlers answer a fragment when `HX-Request: true` and a redirect otherwise. | ||
| 20 | - **Identity later.** Keep the `vole` wordmark as a placeholder and keep colour, | ||
| 21 | type, radius, and spacing in tokens so a real identity is a token swap. | ||
| 22 | - **Single tenant.** No organisation/workspace switcher. The global bar has | ||
| 23 | three destinations; the repository owns its own horizontal tabs. | ||
| 24 | |||
| 25 | ## Navigation model | ||
| 26 | |||
| 27 | Global bar (slim): `Repositories` · `Runners` · account/login. | ||
| 28 | |||
| 29 | Repository header: name, then horizontal tabs directly beneath it: | ||
| 30 | |||
| 31 | `Overview` · `Activity` · `Code` · `Issues` · `Changes` · `Releases` · `Runs` · `Locks` · `Settings` | ||
| 32 | |||
| 33 | No sidebar. A sidebar only pays for its width when there are many persistent | ||
| 34 | destinations; here the repository has seven, and they fit on one row at desktop | ||
| 35 | widths. On narrow screens the row scrolls horizontally rather than collapsing | ||
| 36 | into a menu. | ||
| 37 | |||
| 38 | Overview is the project page: README in the main column; recent commits, recent | ||
| 39 | runs, releases, contributors, languages, and size in the sidebar. Activity is the | ||
| 40 | chronological feed: commits, issues, changes, releases, and runs with type | ||
| 41 | filters. Code is the file browser. Settings appears once the page exists and the | ||
| 42 | viewer has an admin role. | ||
| 43 | |||
| 44 | ## Surfaces | ||
| 45 | |||
| 46 | Status: `done` exists today · `wip` in progress · `todo` missing. | ||
| 47 | |||
| 48 | ### Global | ||
| 49 | |||
| 50 | - done — repository list (`/`) | ||
| 51 | - done — login, registration, enrollment link | ||
| 52 | - done — account (display name, keys, passkeys, sessions) | ||
| 53 | - done — runners and queue | ||
| 54 | - todo — styled 404 / 500 pages everywhere | ||
| 55 | - todo — search across visible repositories | ||
| 56 | - todo — instance-wide activity feed on the home page (after the repo feed) | ||
| 57 | - todo — instance admin: users, invites, keys, repos, permissions | ||
| 58 | - todo — health/status page (currently JSON at `/healthz`) | ||
| 59 | |||
| 60 | ### Repository | ||
| 61 | |||
| 62 | - done — code tab, tree, blob with line numbers, raw | ||
| 63 | - done — commit history, commit view, parent links, diff | ||
| 64 | - done — issues list/detail, comments, filters, close/reopen, labels and assignees (create and edit) | ||
| 65 | - done — changes list/detail, discussion composer, approvals, inline-comment markers, close/reopen | ||
| 66 | - done — releases list/detail, artifacts, archives | ||
| 67 | - done — locks | ||
| 68 | - done — run detail, log, artifacts | ||
| 69 | - done — runs list per repository (`/r/{name}/runs`) with status filters | ||
| 70 | - done — repository activity feed on `/r/{name}/activity`: commits, issues, | ||
| 71 | changes, releases, runs, type filters, and HTMX fragment updates | ||
| 72 | - todo — activity events from metadata-log state changes (closed/reopened), | ||
| 73 | actor filter, and assignee filter | ||
| 74 | - todo — repository settings: description, default branch, ACL, visibility, | ||
| 75 | danger zone | ||
| 76 | - todo — compare view (`/r/{name}/compare/{base}...{head}`) | ||
| 77 | - todo — file history and blame | ||
| 78 | - todo — syntax highlighting in the blob view | ||
| 79 | - todo — language-aware file icons | ||
| 80 | - todo — code search within a repository | ||
| 81 | - todo — release create/edit from the browser (today CLI/tag push) | ||
| 82 | - done — inline review comments on diff lines; the marker appears on the next | ||
| 83 | page load | ||
| 84 | |||
| 85 | ### CI | ||
| 86 | |||
| 87 | - done — runner list, queue, run detail/log/artifacts | ||
| 88 | - todo — workflow/job list per repository | ||
| 89 | - todo — rerun/cancel from the browser (only if the queue model supports it) | ||
| 90 | |||
| 91 | ## Slice order | ||
| 92 | |||
| 93 | 1. ~~**Foundations**~~ — embedded template files, tokenised CSS, slim shell, | ||
| 94 | repo tabs, styled not-found. | ||
| 95 | 2. **Code browsing** — overview/code split, stats, contributors, runs list, | ||
| 96 | tree, line-numbered blobs, commit/diff. Syntax highlighting remains. | ||
| 97 | 3. **Issues and changes** — done: list/detail layout, composer, filters, | ||
| 98 | close/reopen, discussion. Remaining: label/assignee triage, writing inline | ||
| 99 | review comments. | ||
| 100 | 4. **Activity and settings** — repository activity feed, repository settings, | ||
| 101 | ACL editor. | ||
| 102 | 5. **Instance admin** — users, invitations, keys, repositories, permissions. | ||
| 103 | 6. **CI** — workflow/job list, rerun/cancel. | ||
| 104 | 7. **Polish** — search, responsive pass, accessibility pass, light theme, | ||
| 105 | empty states, pagination. | ||
| 106 | |||
| 107 | ## Dynamic layer | ||
| 108 | |||
| 109 | HTMX 2.0.11 is pinned at `server/web/static/htmx.min.js` (Zero-Clause BSD, | ||
| 110 | license beside it). It is served from the embedded static assets and loaded on | ||
| 111 | every page. Interactions are converted one flow at a time: | ||
| 112 | |||
| 113 | - done — issue close/reopen, labels, assignees, and comments update their | ||
| 114 | section in place. Labels and assignees save on token change; there is no | ||
| 115 | visible save button. | ||
| 116 | - done — change approve/close updates the sidebar, and comments update the | ||
| 117 | discussion fragment. | ||
| 118 | - done — Issues and Changes filters swap their list fragment and push the URL. | ||
| 119 | - done — repository activity feed swaps its event fragment for type filters and | ||
| 120 | pushes the URL. | ||
| 121 | - done — diff lines link to an anchored review composer; posted anchors are | ||
| 122 | stored with the reviewed blob. | ||
| 123 | |||
| 124 | The token input and passkey ceremonies stay custom JavaScript: HTMX moves HTML | ||
| 125 | from the server, it does not manage client-side chip state or credential APIs. | ||
| 126 | |||
| 127 | ## Activity feed | ||
| 128 | |||
| 129 | No notification table and no per-user inbox. The feed is derived from Git, the | ||
| 130 | metadata branch, tags, and the run queue. Events carry a type, time, actor, | ||
| 131 | title, link, and (for issue/change events) assignees so the same feed can later | ||
| 132 | be filtered by assignee. | ||
| 133 | |||
| 134 | Repository feed is done on `/r/{name}/activity`; Overview stays the project | ||
| 135 | summary. The home page will become the instance-wide feed over visible | ||
| 136 | repositories. State changes such as closed and reopened need the metadata branch | ||
| 137 | log; the first feed covers creation, comments, approvals, commits, releases, and | ||
| 138 | runs. | ||
| 139 | |||
| 140 | ## Admin scope | ||
| 141 | |||
| 142 | Instance admin covers the operations that are currently CLI-only: | ||
| 143 | |||
| 144 | - users: create by invitation, retire, grant/revoke admin; inspect/revoke keys | ||
| 145 | and sessions | ||
| 146 | - repositories: create, rename, delete | ||
| 147 | - per-repository ACL: set roles, default role, approvals, checks, runners | ||
| 148 | |||
| 149 | The web admin will not ask an admin to paste a user's SSH key. Account creation | ||
| 150 | uses the existing `identity.CreateWithInvite` path and returns a one-time | ||
| 151 | enrollment link; browser auth must be enabled for that link to work. | ||
| 152 | |||
| 153 | Repository admins still own repository settings (description, default branch, | ||
| 154 | visibility, danger zone). Every admin write uses the existing stores, CSRF, and | ||
| 155 | the audit log; the web is a second surface over the same operations, not a | ||
| 156 | second implementation. | ||
| 157 | |||
| 158 | ## Icons | ||
| 159 | |||
| 160 | Icons are Lucide 0.468.0 SVGs (ISC, license at `server/web/static/lucide.LICENSE`), | ||
| 161 | stored as path data in `server/web/icons.go` and rendered inline with | ||
| 162 | `{{icon "name"}}`. No icon font, no CDN, no network request; they inherit | ||
| 163 | `currentColor` and size with the surrounding text. A test checks that every icon | ||
| 164 | named in a template exists. | ||
| 165 | |||
| 166 | ## Rules | ||
| 167 | |||
| 168 | - Every state-changing form carries the CSRF hidden input. | ||
| 169 | - A private repository answers 404, never 403. | ||
| 170 | - No inline CSS or JS in templates; assets live in `server/web/static/`. | ||
| 171 | - Tables are for tabular data; navigation and page chrome are not tables. | ||
| 172 | - Test the page at 1440, 1024, and 390 px wide before calling it done. | ||