docs/superpowers/specs/2026-09-22-parity-followup-design.md

v1.6.1
gitbay-ios/docs/superpowers/specs/2026-09-22-parity-followup-design.md rendered · source · history · blame · raw

220 lines · 10858 bytes

  1# Parity follow-up: gitbay v1.28.1 to v1.34.1
  2
  3An audit on 2026-09-22 of the Parity wiki page (`krz/gitbay`,
  4`.gitbay/wiki/Parity.org`) and of every gitbay release since the app's
  5v1.2.0 (server v1.28.1 through v1.34.1, the last unreleased on main)
  6found one regression, three gaps the page records, two it does not, and
  7one row that is wrong. This spec covers all of them in four releases.
  8
  9Push notifications (v1.32.0, v1.32.1) are already done on main and are
 10not restated here; see `2026-09-20-push-notifications-design.md`.
 11
 12## Findings
 13
 14| # | Finding | Source | Kind |
 15|---|---------|--------|------|
 16| 1 | Profile and org-profile save fail: `unknown flag "--about-format"`, exit 2 | v1.31.0 | regression |
 17| 2 | `build list` returns `subject`; the app does not decode it | v1.33.0 | unrowed gap |
 18| 3 | `build list` pages with `--limit`/`--cursor`; the app does not | v1.33.0 | wrong row (page says yes) |
 19| 4 | MR labels: `mr label --add/--remove` | page | gap |
 20| 5 | MR filter by label: `mr list --label` | page | gap |
 21| 6 | Preview body markup: issue, MR, file editor, release notes | page, v1.30.0 | gap (4 rows) |
 22| 7 | Admin: account list and state filter, promote/demote, disable/enable, worker queues | page, v1.30.0 | gap (4 rows) |
 23
 24Finding 1 was confirmed against gitbay.org. `ProfileEdit.flags()`
 25appends `--about-format <f> --file -` on every save, and
 26`parseProfileFlags` in `internal/control/profile.go` accepts only
 27`--description`, `--website` and `--link`. The shipped 1.2.0 is
 28affected.
 29
 30## Releases
 31
 32| Version | Carries |
 33|---------|---------|
 34| 1.3.0 (12) | push (on main) and finding 1 |
 35| 1.4.0 (13) | findings 2–5 |
 36| 1.5.0 (14) | finding 6 |
 37| 1.6.0 (15) | finding 7 |
 38
 39Each is a squash-merged MR per feature, then a bump MR touching the
 40four `MARKETING_VERSION`/`CURRENT_PROJECT_VERSION` lines, then a
 41lightweight tag on the bump's merge commit.
 42
 43## 1.3.0: profile about
 44
 45The about text has been a file since v1.31.0: `profile/README.md`,
 46`.org` or `.markdown`, resolved in that order, on the default branch of
 47`<owner>/.gitbay`. `profile show` returns it as `about`, `about_format`
 48and `about_path`. `profile set` and `org profile` take description,
 49website and links only.
 50
 51The web's account page no longer edits the text. It links to the file
 52editor and, when there is no file, offers to create the repository with
 53a starter README (`internal/httpd/account.go`, the `profile-repo`
 54form). The app does the same.
 55
 56- `ProfileEdit` drops `about`, `aboutFormat` and `aboutStdin`, and
 57  `flags()` drops the `--about-format`/`--file -` tail.
 58  `ProfileViewModel.saveProfile` stops passing stdin. The same argv
 59  serves `profile set` and `org profile <org>`.
 60- `ProfileEditSheet` drops the about `TextEditor` and format picker.
 61- `Profile` decodes `about_path`. `aboutFile` is deleted;
 62  `ReadmeView` receives `aboutPath`, so the file's extension picks the
 63  renderer.
 64- `ProfileView`'s about section, for a viewer where `canEdit` is true,
 65  gains an Edit action that pushes
 66  `.file(repo: "<owner>/.gitbay", path: aboutPath, ref: nil)`. The file
 67  screen's existing edit action and `repo commit-file` do the write.
 68- With no `about_path` and `canEdit` true, the section offers "Create
 69  about file". It runs `repo show <owner>/.gitbay`; on exit 3 (not
 70  found) it runs `repo create <owner>/.gitbay`. It then runs
 71  `repo commit-file <owner>/.gitbay profile/README.md --ref <branch>
 72  --message "add profile about" --file -`, where `<branch>` is the
 73  existing repository's `default_branch` from `repo show` (the server
 74  reads the about file from the default branch) or `main` for a
 75  repository just created, with the web's starter text
 76  (`# <owner>\n\nThis is the about text on your profile.\n`), and
 77  reloads. Any other failure is shown and stops the flow. `repo create` accepts `<org>/.gitbay` from an org admin
 78  (`resolveNewRepoOwner`, `internal/control/repo.go`), so the offer is
 79  the same for users and organizations.
 80
 81Tests: `ProfileEdit.flags()` carries no `--about-format` or `--file`;
 82`saveProfile` sends no stdin; `Profile` decodes `about_path`; the
 83create flow issues `repo create` then `repo commit-file` with the
 84starter on stdin, and skips `repo create` when `repo show` succeeds. The live
 85suite's profile step saves a description and asserts success.
 86
 87## 1.4.0: build list and MR labels
 88
 89### Build list
 90
 91- `Build` gains `subject: String?`.
 92- `BuildRow` leads with `#<n> <subject>`, falling back to
 93  `#<n> <shortSHA>` when the commit is gone. The meta line carries job,
 94  ref and `shortSHA`.
 95- `BuildListViewModel` replaces `readList` with
 96  `PagedListModel<Build>(pageSize: 30)`, the web's page size. The
 97  `sorted { $0.number > $1.number }` goes: cursor pages arrive in
 98  server order and a client sort would reorder across page boundaries.
 99  A filter change sets `argv` and calls `reload()`.
100- `BuildListView` gains `PageFooter`, as the issue and MR lists have.
101
102`load()` never set `.loading` before; reload tests wait on the final
103state, not on "not loading".
104
105### MR labels
106
107A copy of the issue implementation.
108
109- `MRDetailViewModel` loads `label list <repo>` for the chips' colours
110  and gains `addLabel`/`removeLabel`, sending
111  `mr label <repo> <n> --add|--remove <l>`, as `IssueDetailViewModel`
112  does for `issue label`.
113- `MRView` shows the label chips, each removable, and the add field
114  `IssueView` has.
115- `MRFilter` gains `label: String?`, rendered as `--label`. Its doc
116  comment, which says the command takes no `--label`, is corrected.
117  The MR filter sheet gains the label picker the issue sheet has.
118
119Tests: `Build` decodes with and without `subject`; the row title
120falls back; the build list sends `--limit 30`, appends the second page
121on `loadMore`, and does not reorder; `MRFilter.flags()` with a label;
122`mr label` argv for add and remove.
123
124## 1.5.0: markup previews
125
126The web previews by posting the draft back to its own form, where the
127server renders it with autolinks resolved for the viewer. No command
128renders markup (the CLI row is `n/a`), and the app resolves no
129autolinks anywhere. The app previews locally with `FormattedBodyView`,
130the view its threads, bodies and comments already render with. The
131preview therefore matches what the app shows, not what the web shows:
132`#N` stays plain text in both the preview and the thread.
133
134- New `MarkupPreview(text:format:identifier:)` in
135  `gitbay/Views/Shared/`: a collapsed "Preview" disclosure row that
136  renders `FormattedBodyView(text:format:)` when expanded, and "Nothing
137  to preview." for blank text. It sits under the editor rather than
138  replacing it, so the draft stays visible and editable, as on the web.
139- `MarkupPreview.format(forFile:)` maps a file name to the format its
140  contents render in: `org` for `.org`, `md` for `.md` and
141  `.markdown`, nil for anything else.
142- Call sites, each passing the format it already sends:
143  - `ComposeSheet`'s body: issue create, issue and MR edit, release
144    edit. The format follows the sheet's `format` binding, which is
145    `md` where the sheet shows no picker.
146  - `MRCreateSheet` in `MRListView`: its own `format`.
147  - The comment rows in `IssueView` and `MRView`: their
148    `commentFormat` picker. Shown only while the comment is non-empty.
149  - `ReleaseCreateSheet` in `ReleaseListView`: `md`, since
150    `release create` sends no `--format` and the app renders notes as
151    markdown.
152  - `FileEditSheet`: only when `format(forFile:)` is non-nil, with that
153    format.
154
155Tests: `format(forFile:)` for `.org`, `.md`, `.markdown`, `.MD`,
156`.swift` and a name without an extension. The live suite types a
157comment on the fixture issue, expands its preview and finds the
158rendered text, then clears the field without sending.
159
160## 1.6.0: admin
161
162Matches the web's `/admin` and `/admin/users` and nothing else. Account
163show, invite, runners, repository administration, the audit log and
164instance statistics stay `no` on both surfaces.
165
166- **Who sees it.** `whoami` returns `admin`. The My Profile tab reads it
167  when it appears (and again on an account switch) and passes it to
168  `AccountMenu`, which shows Admin only when it is true. The menu has no
169  model of its own and the dashboard's data lives on another tab, so
170  this is the one read that reaches it; `SessionStore` is unchanged.
171- **The admin screen** reads `dashboard` and decodes its admin-only
172  `queues` and `server` blocks, the read the web's `/admin` page makes
173  (`internal/httpd/admin.go`). It shows the server commit, a link to
174  Accounts, and the web's six sections in the web's order — webhook
175  deliveries, mail, push, mirrors, builds, dependency checks — each with
176  its heading count, its counts line, its retrying or failed rows, and
177  the web's empty-queue sentence. A dashboard without `queues` (the
178  caller is not an admin) is an empty state saying so.
179- **Accounts** pages `admin user list [--state active|pending|disabled|admin]`
180  through `PagedListModel`, with the web's five filters (all, active,
181  pending, disabled, admins) as a segmented picker. Each row carries the
182  web's two actions, in a per-row menu: Demote for an admin, Promote
183  for an active account and a disabled Promote for anyone else; Enable
184  for a disabled account and Disable otherwise. Each sends
185  `admin user promote|demote|disable|enable <username>` and reloads.
186- Demote and disable ask for the username to be typed before they
187  send, as the web does. Promote and enable ask with a plain alert.
188
189Tests: the six sections' headings, counts, summaries and row text from
190a recorded payload; a dashboard without `queues`; `whoami`'s `admin`,
191including a failed read counting as not admin; each row state's two
192actions; `admin user list` argv per filter and across a page; each
193write's argv and its reload; a refusal surfacing. The live suite
194asserts that `ios-smoke`, which is not an admin, is not offered Admin.
195
196## Upstream: krz/gitbay
197
198One MR alongside 1.3.0:
199
200- Parity: iOS `build list paging (limit, cursor)` from `yes` to `no`;
201  a row for "build row names its commit" (cli yes, web yes, ios no);
202  a sentence under the preview paragraph that the iOS preview is the
203  app's own rendering and resolves no autolinks.
204- The published v1.31.0 release notes give the about file as
205  `about.org` or `about.md`; they are edited with `release edit` to
206  `profile/README.md` or `profile/README.org`. The CHANGELOG entry for
207  v1.31.0 already names the right path and is unchanged.
208
209Later MRs flip rows as each release ships: the build rows at 1.4.0,
210MR labels and filter by label at 1.4.0, the four preview rows at 1.5.0,
211and account list, promote/demote, disable/enable and worker queues at
2121.6.0.
213
214## Not in scope
215
216- Autolink resolution in the app.
217- A server-side `render` command.
218- Admin beyond what the web carries.
219- Bookmarks as a profile section: the app keeps them as their own
220  screen, and the Parity row stays `no`.