# 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 --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 `/.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. - `ProfileEdit` drops `about`, `aboutFormat` and `aboutStdin`, and `flags()` drops the `--about-format`/`--file -` tail. `ProfileViewModel.saveProfile` stops passing stdin. The same argv serves `profile set` and `org profile `. - `ProfileEditSheet` drops the about `TextEditor` and format picker. - `Profile` decodes `about_path`. `aboutFile` is deleted; `ReadmeView` receives `aboutPath`, so the file's extension picks the renderer. - `ProfileView`'s about section, for a viewer where `canEdit` is true, gains an Edit action that pushes `.file(repo: "/.gitbay", path: aboutPath, ref: nil)`. The file screen's existing edit action and `repo commit-file` do the write. - With no `about_path` and `canEdit` true, the section offers "Create about file". It runs `repo show /.gitbay`; on exit 3 (not found) it runs `repo create /.gitbay`. It then runs `repo commit-file /.gitbay profile/README.md --ref --message "add profile about" --file -`, where `` is the existing repository's `default_branch` from `repo show` (the server reads the about file from the default branch) or `main` for a repository just created, with the web's starter text (`# \n\nThis is the about text on your profile.\n`), and reloads. Any other failure is shown and stops the flow. `repo create` accepts `/.gitbay` from 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 - `Build` gains `subject: String?`. - `BuildRow` leads with `# `, falling back to `# ` when the commit is gone. The meta line carries job, ref and `shortSHA`. - `BuildListViewModel` replaces `readList` with `PagedListModel(pageSize: 30)`, the web's page size. The `sorted { $0.number > $1.number }` goes: cursor pages arrive in server order and a client sort would reorder across page boundaries. A filter change sets `argv` and calls `reload()`. - `BuildListView` gains `PageFooter`, 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. - `MRDetailViewModel` loads `label list ` for the chips' colours and gains `addLabel`/`removeLabel`, sending `mr label --add|--remove `, as `IssueDetailViewModel` does for `issue label`. - `MRView` shows the label chips, each removable, and the add field `IssueView` has. - `MRFilter` gains `label: 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:)` in `gitbay/Views/Shared/`: a collapsed "Preview" disclosure row that renders `FormattedBodyView(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: `org` for `.org`, `md` for `.md` and `.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's `format` binding, which is `md` where the sheet shows no picker. - `MRCreateSheet` in `MRListView`: its own `format`. - The comment rows in `IssueView` and `MRView`: their `commentFormat` picker. Shown only while the comment is non-empty. - `ReleaseCreateSheet` in `ReleaseListView`: `md`, since `release create` sends no `--format` and the app renders notes as markdown. - `FileEditSheet`: only when `format(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.** `whoami` returns `admin`. The My Profile tab reads it when it appears (and again on an account switch) and passes it to `AccountMenu`, 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; `SessionStore` is unchanged. - **The admin screen** reads `dashboard` and decodes its admin-only `queues` and `server` blocks, the read the web's `/admin` page 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 without `queues` (the caller is not an admin) is an empty state saying so. - **Accounts** pages `admin user list [--state active|pending|disabled|admin]` through `PagedListModel`, 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 sends `admin user promote|demote|disable|enable ` 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)` from `yes` to `no`; 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.org` or `about.md`; they are edited with `release edit` to `profile/README.md` or `profile/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 `render` command. - 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`.