docs/superpowers/specs/2026-09-22-parity-followup-design.md
220 lines · 10858 bytes
11 symbols in this file
Parity follow-up: gitbay v1.28.1 to v1.34.1
An audit on 2026-09-22 of the Parity wiki page (krz/gitbay,
.gitbay/wiki/Parity.org) and of every gitbay release since the app's
v1.2.0 (server v1.28.1 through v1.34.1, the last unreleased on main)
found one regression, three gaps the page records, two it does not, and
one row that is wrong. This spec covers all of them in four releases.
Push notifications (v1.32.0, v1.32.1) are already done on main and are
not restated here; see 2026-09-20-push-notifications-design.md.
Findings
| # | Finding | Source | Kind |
|---|---|---|---|
| 1 | Profile and org-profile save fail: unknown flag "--about-format", exit 2 |
v1.31.0 | regression |
| 2 | build list returns subject; the app does not decode it |
v1.33.0 | unrowed gap |
| 3 | build list pages with --limit/--cursor; the app does not |
v1.33.0 | wrong row (page says yes) |
| 4 | MR labels: mr label --add/--remove |
page | gap |
| 5 | MR filter by label: mr list --label |
page | gap |
| 6 | Preview body markup: issue, MR, file editor, release notes | page, v1.30.0 | gap (4 rows) |
| 7 | Admin: account list and state filter, promote/demote, disable/enable, worker queues | page, v1.30.0 | gap (4 rows) |
Finding 1 was confirmed against gitbay.org. ProfileEdit.flags()
appends --about-format <f> --file - on every save, and
parseProfileFlags in internal/control/profile.go accepts only
--description, --website and --link. The shipped 1.2.0 is
affected.
Releases
| Version | Carries |
|---|---|
| 1.3.0 (12) | push (on main) and finding 1 |
| 1.4.0 (13) | findings 2–5 |
| 1.5.0 (14) | finding 6 |
| 1.6.0 (15) | finding 7 |
Each is a squash-merged MR per feature, then a bump MR touching the
four MARKETING_VERSION/CURRENT_PROJECT_VERSION lines, then a
lightweight tag on the bump's merge commit.
1.3.0: profile about
The about text has been a file since v1.31.0: profile/README.md,
.org or .markdown, resolved in that order, on the default branch of
<owner>/.gitbay. profile show returns it as about, about_format
and about_path. profile set and org profile take description,
website and links only.
The web's account page no longer edits the text. It links to the file
editor and, when there is no file, offers to create the repository with
a starter README (internal/httpd/account.go, the profile-repo
form). The app does the same.
ProfileEditdropsabout,aboutFormatandaboutStdin, andflags()drops the--about-format/--file -tail.ProfileViewModel.saveProfilestops passing stdin. The same argv servesprofile setandorg profile <org>.ProfileEditSheetdrops the aboutTextEditorand format picker.Profiledecodesabout_path.aboutFileis deleted;ReadmeViewreceivesaboutPath, so the file's extension picks the renderer.ProfileView's about section, for a viewer wherecanEditis true, gains an Edit action that pushes.file(repo: "<owner>/.gitbay", path: aboutPath, ref: nil). The file screen's existing edit action andrepo commit-filedo the write.- With no
about_pathandcanEdittrue, the section offers "Create about file". It runsrepo show <owner>/.gitbay; on exit 3 (not found) it runsrepo create <owner>/.gitbay. It then runsrepo commit-file <owner>/.gitbay profile/README.md --ref <branch> --message "add profile about" --file -, where<branch>is the existing repository'sdefault_branchfromrepo show(the server reads the about file from the default branch) ormainfor a repository just created, with the web's starter text (# <owner>\n\nThis is the about text on your profile.\n), and reloads. Any other failure is shown and stops the flow.repo createaccepts<org>/.gitbayfrom an org admin (resolveNewRepoOwner,internal/control/repo.go), so the offer is the same for users and organizations.
Tests: ProfileEdit.flags() carries no --about-format or --file;
saveProfile sends no stdin; Profile decodes about_path; the
create flow issues repo create then repo commit-file with the
starter on stdin, and skips repo create when repo show succeeds. The live
suite's profile step saves a description and asserts success.
1.4.0: build list and MR labels
Build list
Buildgainssubject: String?.BuildRowleads with#<n> <subject>, falling back to#<n> <shortSHA>when the commit is gone. The meta line carries job, ref andshortSHA.BuildListViewModelreplacesreadListwithPagedListModel<Build>(pageSize: 30), the web's page size. Thesorted { $0.number > $1.number }goes: cursor pages arrive in server order and a client sort would reorder across page boundaries. A filter change setsargvand callsreload().BuildListViewgainsPageFooter, as the issue and MR lists have.
load() never set .loading before; reload tests wait on the final
state, not on "not loading".
MR labels
A copy of the issue implementation.
MRDetailViewModelloadslabel list <repo>for the chips' colours and gainsaddLabel/removeLabel, sendingmr label <repo> <n> --add|--remove <l>, asIssueDetailViewModeldoes forissue label.MRViewshows the label chips, each removable, and the add fieldIssueViewhas.MRFiltergainslabel: String?, rendered as--label. Its doc comment, which says the command takes no--label, is corrected. The MR filter sheet gains the label picker the issue sheet has.
Tests: Build decodes with and without subject; the row title
falls back; the build list sends --limit 30, appends the second page
on loadMore, and does not reorder; MRFilter.flags() with a label;
mr label argv for add and remove.
1.5.0: markup previews
The web previews by posting the draft back to its own form, where the
server renders it with autolinks resolved for the viewer. No command
renders markup (the CLI row is n/a), and the app resolves no
autolinks anywhere. The app previews locally with FormattedBodyView,
the view its threads, bodies and comments already render with. The
preview therefore matches what the app shows, not what the web shows:
#N stays plain text in both the preview and the thread.
- New
MarkupPreview(text:format:identifier:)ingitbay/Views/Shared/: a collapsed "Preview" disclosure row that rendersFormattedBodyView(text:format:)when expanded, and "Nothing to preview." for blank text. It sits under the editor rather than replacing it, so the draft stays visible and editable, as on the web. MarkupPreview.format(forFile:)maps a file name to the format its contents render in:orgfor.org,mdfor.mdand.markdown, nil for anything else.- Call sites, each passing the format it already sends:
ComposeSheet's body: issue create, issue and MR edit, release edit. The format follows the sheet'sformatbinding, which ismdwhere the sheet shows no picker.MRCreateSheetinMRListView: its ownformat.- The comment rows in
IssueViewandMRView: theircommentFormatpicker. Shown only while the comment is non-empty. ReleaseCreateSheetinReleaseListView:md, sincerelease createsends no--formatand the app renders notes as markdown.FileEditSheet: only whenformat(forFile:)is non-nil, with that format.
Tests: format(forFile:) for .org, .md, .markdown, .MD,
.swift and a name without an extension. The live suite types a
comment on the fixture issue, expands its preview and finds the
rendered text, then clears the field without sending.
1.6.0: admin
Matches the web's /admin and /admin/users and nothing else. Account
show, invite, runners, repository administration, the audit log and
instance statistics stay no on both surfaces.
- Who sees it.
whoamireturnsadmin. The My Profile tab reads it when it appears (and again on an account switch) and passes it toAccountMenu, which shows Admin only when it is true. The menu has no model of its own and the dashboard's data lives on another tab, so this is the one read that reaches it;SessionStoreis unchanged. - The admin screen reads
dashboardand decodes its admin-onlyqueuesandserverblocks, the read the web's/adminpage makes (internal/httpd/admin.go). It shows the server commit, a link to Accounts, and the web's six sections in the web's order — webhook deliveries, mail, push, mirrors, builds, dependency checks — each with its heading count, its counts line, its retrying or failed rows, and the web's empty-queue sentence. A dashboard withoutqueues(the caller is not an admin) is an empty state saying so. - Accounts pages
admin user list [--state active|pending|disabled|admin]throughPagedListModel, with the web's five filters (all, active, pending, disabled, admins) as a segmented picker. Each row carries the web's two actions, in a per-row menu: Demote for an admin, Promote for an active account and a disabled Promote for anyone else; Enable for a disabled account and Disable otherwise. Each sendsadmin user promote|demote|disable|enable <username>and reloads. - Demote and disable ask for the username to be typed before they send, as the web does. Promote and enable ask with a plain alert.
Tests: the six sections' headings, counts, summaries and row text from
a recorded payload; a dashboard without queues; whoami's admin,
including a failed read counting as not admin; each row state's two
actions; admin user list argv per filter and across a page; each
write's argv and its reload; a refusal surfacing. The live suite
asserts that ios-smoke, which is not an admin, is not offered Admin.
Upstream: krz/gitbay
One MR alongside 1.3.0:
- Parity: iOS
build list paging (limit, cursor)fromyestono; a row for "build row names its commit" (cli yes, web yes, ios no); a sentence under the preview paragraph that the iOS preview is the app's own rendering and resolves no autolinks. - The published v1.31.0 release notes give the about file as
about.orgorabout.md; they are edited withrelease edittoprofile/README.mdorprofile/README.org. The CHANGELOG entry for v1.31.0 already names the right path and is unchanged.
Later MRs flip rows as each release ships: the build rows at 1.4.0, MR labels and filter by label at 1.4.0, the four preview rows at 1.5.0, and account list, promote/demote, disable/enable and worker queues at 1.6.0.
Not in scope
- Autolink resolution in the app.
- A server-side
rendercommand. - Admin beyond what the web carries.
- Bookmarks as a profile section: the app keeps them as their own
screen, and the Parity row stays
no.