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.

Give every account a profile page and a directory of accounts

a6b42433c087ab0c936e34a1d05cf488db7a92d6 · Keenan Nemetz · 8d ago · parent 727df386

Give every account a profile page and a directory of accounts

Clicking a name now opens /u/<name>: the issues and changes somebody
opened, the reviews they gave, the comments they left, and the
repositories that work is in. The numbers and the feed are grouped reads
of the same event rows the activity feed pages through, so a profile
cannot disagree with the feed, and every read is limited to the
repositories the reader may open. A private repository's work is not a
number an outsider gets to learn, so two readers can see different
totals and the page says what its numbers cover.

/u lists the roster. Account names are public on this instance; their
activity is not. Both pages ask crawlers to leave them out of an index.

@name in an issue, a comment, a change request, or a release note links
to the account it addresses, using the mention scanner CI already
evaluates: what reads as an address on the page is what a workflow sees.
A name that is not an account stays text, and code samples are skipped.

Git author names are not linked and commit counts are not shown. A
commit author is a name somebody typed, not the credential that proves
an account, and the index only keeps the top eight authors of each
default branch anyway.

server/web/static/vole.css +55

@@ -2883,3 +2883,58 @@ textarea:focus-visible {
2883 2883 .lang-dot.lang-sql { background: #e38c00; }
2884 2884 .lang-dot.lang-image { background: #a074c4; }
2885 2885 .lang-dot.lang-lock, .lang-dot.lang-build { background: var(--fg-muted); }
2886
2887 /* ---------- profiles and the account directory ---------- */
2888
2889 .profile-head { align-items: flex-start; }
2890 .profile-back { margin: 0 0 6px; font-size: calc(12.65px * var(--font-scale)); }
2891 .profile-who { display: flex; align-items: center; gap: 14px; min-width: 0; }
2892 .profile-joined { margin: 4px 0 0; font-size: calc(12.65px * var(--font-scale)); }
2893 .profile-scope { margin: 8px 0 0; font-size: calc(12.65px * var(--font-scale)); }
2894
2895 .avatar-lg { width: 52px; height: 52px; font-size: calc(22px * var(--font-scale)); }
2896
2897 /* The four numbers a profile leads with. Each one is a link to the feed it
2898 counts, so a number is a door rather than a dead end. */
2899 .stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 10px; margin: 18px 0 0; }
2900 .stat {
2901 display: flex;
2902 flex-direction: column;
2903 gap: 3px;
2904 padding: 14px 16px;
2905 color: var(--fg);
2906 background: var(--bg-panel);
2907 border: 1px solid var(--border);
2908 border-radius: var(--radius-lg);
2909 }
2910 .stat:hover { color: var(--fg); background: var(--bg-raised); border-color: var(--border-strong); text-decoration: none; }
2911 .stat-num { font-size: calc(24px * var(--font-scale)); font-weight: 600; font-variant-numeric: tabular-nums; }
2912 .stat-label { color: var(--fg-muted); font-size: calc(12.65px * var(--font-scale)); }
2913
2914 .profile-repos, .profile-activity { margin-top: 26px; }
2915 .profile-repo { gap: 4px; }
2916 .profile-repo-meta { display: flex; flex-wrap: wrap; gap: 4px 10px; margin: 0; color: var(--fg-muted); font-size: calc(12.65px * var(--font-scale)); }
2917
2918 /* One row per account. A list rather than a table: a row says what someone has
2919 been working on in a sentence, and a name is the only thing worth a column. */
2920 .people-list { list-style: none; margin: 10px 0 0; padding: 0; }
2921 .people-row {
2922 display: flex;
2923 align-items: center;
2924 gap: 11px;
2925 padding: 10px 2px;
2926 border-bottom: 1px solid var(--border);
2927 min-width: 0;
2928 }
2929 .people-row:last-child { border-bottom: 0; }
2930 .people-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
2931 .people-name { font-weight: 600; }
2932 .people-summary { color: var(--fg-muted); font-size: calc(12.65px * var(--font-scale)); }
2933 .people-joined { font-size: calc(12.65px * var(--font-scale)); white-space: nowrap; }
2934 .people-retired .people-name { color: var(--fg-muted); }
2935 .people-row .label { flex: 0 0 auto; }
2936
2937 @media (max-width: 720px) {
2938 .people-joined { display: none; }
2939 .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2940 }