Commit 902739d68c

902739d68c6e645492ef50f89e8cae72661b694b

parent: 2dd2564d2a

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-20 00:01 UTC

docs: spec for moving the profile about text into a repository

Ref #236
docs/specs/2026-09-19-profile-about-repo-design.md added +200
@@ -0,0 +1,200 @@
1# Profile about text in a repository
2
3Closes #236.
4
5The `about` text on a user or org profile is a column on `users`/`orgs`,
6set through `profile set --about` and rendered by `aboutHTML`. It is the
7only long-form, version-worthy prose on the instance that is not a file
8in a repository. This moves it into one.
9
10Links, description and website stay where they are. The issue lists
11links as a "consider"; moving them buys nothing the DB columns do not
12already give, and a second file or a front-matter parser is cost without
13a return.
14
15## Where it lives
16
17`profile/README.md` or `profile/README.org` on the default branch of a
18repository named `.gitbay` under the owner's namespace:
19
20```
21cmc/.gitbay
22└── profile/
23 └── README.org
24```
25
26Resolution order is the wiki's `wikiExts`: `.md`, `.org`, `.markdown`;
27the first that exists wins. There are no store rows for the file —
28access derives from the parent repository, exactly as
29`internal/control/wiki.go` states for wiki pages.
30
31A dot-repo rather than the GitHub-style `cmc/cmc` because `.gitbay` is
32not single-purpose: it is the place later per-owner configuration
33(issue templates, org defaults) goes, and a leading dot is the signal
34that it is infrastructure rather than a project.
35
36### Reading
37
38A helper in `internal/control/profile.go`:
39
40```go
41// ownerAbout returns the about text and its format for an owner, read
42// from profile/README.* on the default branch of <owner>/.gitbay.
43// Everything missing — the repo, the branch, the file — is an empty
44// about, as is a repo the caller cannot read.
45func ownerAbout(c *Ctx, owner string) (text, format string)
46```
47
48It resolves `<owner>/.gitbay` through the same access check every other
49read takes, so a repository the caller cannot read yields no about. The
50blob read is capped at `maxCommitFileBytes` (1MB).
51
52`ProfileOut.About` and `ProfileOut.AboutFormat` keep their JSON names
53and meanings; only the source changes. `AboutFormat` is `org` for a
54`.org` file and `md` otherwise. The API contract and the iOS client are
55untouched.
56
57`aboutHTML` in `internal/httpd/web.go` becomes a direct
58`renderReadme(name, raw)` call — there is a filename to dispatch on now,
59so the stored-format indirection goes away.
60
61## Naming
62
63`policy.namePat` requires a leading alphanumeric, so `.gitbay` is an
64invalid repository name today:
65
66```go
67var namePat = regexp.MustCompile(`^\.?[a-z0-9][a-z0-9._-]{0,61}$`)
68```
69
70One optional leading dot, same 63-character ceiling. `ValidateName`
71keeps refusing `.`, `..` and a `.git` suffix, and gains an exact-`.git`
72refusal — the suffix rule only fires for names longer than four
73characters.
74
75Relaxing the pattern rather than whitelisting the one name `.gitbay` is
76the smaller change, and it gives owners `.dotfiles` and the like for
77free.
78
79## Writing
80
81There is no about-specific write command, for the reason the wiki has
82none: the content is a file, and the file is written the way files are
83written.
84
85- `profile set` loses `--about`, `--about-format` and `--file`, and
86 loses `ReadsStdin`.
87- `org profile` loses the same three flags.
88- Authoring is a push, or
89 `repo commit-file cmc/.gitbay profile/README.org --ref main --file -`.
90
91### The web
92
93The About textarea on the account page is replaced by a line naming the
94file and linking to it, with a button that creates `<owner>/.gitbay`
95and commits a starter `profile/README.md` when the repository does not
96exist yet. Editing then happens in the repository file editor that
97already exists.
98
99The alternative — keeping the textarea and dispatching
100`repo commit-file` from it — needs an auto-create path and has a stale
101file problem: moving the format picker from md to org leaves a
102`README.md` that keeps winning resolution, and `commit-file` cannot
103delete it, so the handler needs a second dispatch to `file remove`. The
104pointer is smaller and matches how a wiki page is edited.
105
106The account form's `profile` case keeps `--description`, `--website`
107and `--link`, and stops sending `--about-format` and stdin. The Preview
108button on that form goes with the textarea; the repository file editor
109has its own.
110
111## Access and visibility
112
113The about renders to whoever can read `<owner>/.gitbay`. A private
114`.gitbay` means the about is visible to the owner and admins only. That
115is the parent-derived rule already in force for wiki pages, not a new
116one.
117
118The backfill creates the repository **public**, so no about that was
119world-readable becomes hidden by the move.
120
121Repositories whose name starts with `.` are filtered out of:
122
123- the profile page's repository list (`ProfileOut.Repos`), and
124- `explore`.
125
126They stay in `repo list`, which is the owner's own inventory, and stay
127reachable at their URL. Hiding the repository is what a dot-repo buys
128over `cmc/cmc`; without the filter the move trades one visible
129single-purpose repository for another.
130
131## Migration
132
133A SQL migration cannot write git objects, so the move is two pieces
134that ship together. `gitbayd` runs `MigrateUp` at startup, so a backfill
135that reads the columns must not run after a migration that drops them —
136hence the holding table.
137
138**Migration 0058** copies every owner with a non-empty about into a
139holding table, then drops the columns:
140
141```sql
142CREATE TABLE profile_about_backfill (
143 owner_kind TEXT NOT NULL,
144 owner_id INTEGER NOT NULL,
145 about TEXT NOT NULL,
146 about_format TEXT NOT NULL,
147 PRIMARY KEY (owner_kind, owner_id)
148);
149INSERT INTO profile_about_backfill ... -- users, then orgs
150ALTER TABLE users DROP COLUMN about; -- and about_format
151ALTER TABLE orgs DROP COLUMN about; -- and about_format
152```
153
154The down migration re-adds the columns empty and drops the table.
155
156**`gitbayd admin migrate-profile-about`**, in the shape of
157`adminMigrateCommitRefsCmd` in `cmd/gitbayd/adminusers.go`, drains the
158table. Per row: create `<owner>/.gitbay` public if it does not exist,
159`gitutil.CommitFileChange` the README at the recorded format, delete
160the row. Idempotent — an owner who already has the file is skipped and
161their row deleted.
162
163Commit identity is the owner's primary verified email when they have
164one, otherwise `<name>@users.noreply.<host>`. Orgs have no email and
165always take the fallback.
166
167A later release drops the emptied holding table.
168
169## Testing
170
171Unit:
172
173- `policy`: `.gitbay` and `.dotfiles` accepted; `.`, `..`, `.git` and
174 `x.git` still refused; the 63-character ceiling holds with the dot.
175- `ownerAbout`: `.md` wins over `.org`; a missing repo, a missing
176 branch and a missing file each give an empty about; a repo the caller
177 cannot read gives an empty about.
178
179e2e, new `e2e/profileabout_test.go`:
180
181- a committed `profile/README.md` shows on `profile show` and on the
182 web profile page;
183- a private `.gitbay` hides the about from an outsider while the owner
184 still sees it;
185- dot-repos do not appear in `explore` or in a profile's repository
186 list, and do appear in `repo list`;
187- `profile set --about` is refused as an unknown flag.
188
189e2e for the backfill in the shape of `e2e/commentmigrate_test.go`:
190a row in the holding table becomes a repository with the file, and a
191second run is a no-op.
192
193`TestStdinCommandsReadStdin` already polices `profile set` dropping
194`ReadsStdin`.
195
196## Documentation
197
198- `.gitbay/wiki/Parity` — the profile row.
199- `.gitbay/wiki/Users` — the profile section: where the about lives and
200 how to write it.