Commit 69ddde3892
Verified · cmc
Layout: unified · split
docs/superpowers/plans/2026-09-22-1.5.0-markup-previews.md added +433
| @@ -0,0 +1,433 @@ | ||
| 1 | # 1.5.0 Markup Previews Implementation Plan | |
| 2 | ||
| 3 | > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. | |
| 4 | ||
| 5 | **Goal:** Every editor that takes markup — issue and MR bodies and comments, release notes, and a markup file in the file editor — can show its draft rendered before it is sent. | |
| 6 | ||
| 7 | **Architecture:** One shared view, `MarkupPreview`, a collapsed "Preview" disclosure row placed under each editor. Expanded, it renders the draft with `FormattedBodyView`, the view the app's threads already render bodies and comments with, so the preview is what the app will show. A static `format(forFile:)` decides whether a file in the editor renders at all, and as which format. | |
| 8 | ||
| 9 | **Tech Stack:** Swift 6 language mode with default `MainActor` isolation, SwiftUI, Swift Testing, XCUITest. iOS 18.0 minimum, iOS 26.5 SDK. Rendering through `OrgSwiftUI` and the app's `MarkdownView`. | |
| 10 | ||
| 11 | **Spec:** `docs/superpowers/specs/2026-09-22-parity-followup-design.md` (section "1.5.0: markup previews") | |
| 12 | ||
| 13 | ## Global Constraints | |
| 14 | ||
| 15 | - **Never attribute anything to an assistant or model** — not in commits, code comments, MR bodies, or docs. No `Co-Authored-By` trailer. This is absolute. | |
| 16 | - **Commits are signed.** `commit.gpgsign` is on; do not pass `--no-gpg-sign`. | |
| 17 | - **Never push to `main`. Never merge.** One branch, `markup-preview`, cut from `main` after 1.4.0 has merged. Task 4 is the version bump and waits for the user to merge it. | |
| 18 | - **File-system-synchronized groups.** New `.swift` files under `gitbay/` and `gitbayTests/` are picked up automatically. Do not edit `project.pbxproj` except for the version lines in Task 4. | |
| 19 | - **The preview promises the app's rendering, not the web's.** Neither the app's threads nor the preview resolve autolinks (`#N`, `owner/name#N`). Say nothing in copy or comments that implies otherwise. | |
| 20 | - **Design tokens only.** Fonts are `.gbSans(…)`/`.gbMono(…)`; colours come from the `gb` palette or the system semantic styles. No new visual treatment: a `DisclosureGroup` row in the existing `Form`/`List` is the whole UI. | |
| 21 | - Comments explain *why*, in plain direct English, at the density of the surrounding code. No before/after commentary. | |
| 22 | - **Run the whole unit target, and read the count.** | |
| 23 | ||
| 24 | ```bash | |
| 25 | cd /Users/cmc/git/krz/gitbay-ios && rm -rf /tmp/gb.xcresult && xcodebuild -project gitbay.xcodeproj -scheme gitbay \ | |
| 26 | -destination 'id=0A03DF15-0F45-4726-8080-DBFE919F46B6' -only-testing:gitbayTests \ | |
| 27 | -resultBundlePath /tmp/gb.xcresult test 2>&1 | grep -E "error:|failed|TEST (SUCCEEDED|FAILED)" | tail -20 | |
| 28 | xcrun xcresulttool get test-results summary --path /tmp/gb.xcresult --format json \ | |
| 29 | | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['result'], d['totalTestCount'], d['failedTests'])" | |
| 30 | ``` | |
| 31 | ||
| 32 | "Run the unit target" below means these two commands. Record the count before Task 1 as `BASE`. | |
| 33 | ||
| 34 | --- | |
| 35 | ||
| 36 | ### Task 1: `MarkupPreview` | |
| 37 | ||
| 38 | **Files:** | |
| 39 | - Create: `gitbay/Views/Shared/MarkupPreview.swift` | |
| 40 | - Create: `gitbayTests/MarkupPreviewTests.swift` | |
| 41 | ||
| 42 | **Interfaces:** | |
| 43 | - Consumes: `FormattedBodyView(text: String, format: String?)` in `gitbay/Views/Shared/`. | |
| 44 | - Produces: `MarkupPreview(text: String, format: String?, identifier: String)`; `static func MarkupPreview.format(forFile name: String) -> String?` returning `"org"`, `"md"` or `nil`. | |
| 45 | ||
| 46 | - [ ] **Step 1: Branch** | |
| 47 | ||
| 48 | ```bash | |
| 49 | cd /Users/cmc/git/krz/gitbay-ios && git switch main && git pull --ff-only && git switch -c markup-preview | |
| 50 | ``` | |
| 51 | ||
| 52 | Run the unit target and record the count as `BASE`. | |
| 53 | ||
| 54 | - [ ] **Step 2: Write the failing tests** | |
| 55 | ||
| 56 | Create `gitbayTests/MarkupPreviewTests.swift`: | |
| 57 | ||
| 58 | ```swift | |
| 59 | import Testing | |
| 60 | @testable import gitbay | |
| 61 | ||
| 62 | struct MarkupPreviewFormatTests { | |
| 63 | ||
| 64 | @Test func orgFilesRenderAsOrg() { | |
| 65 | #expect(MarkupPreview.format(forFile: "README.org") == "org") | |
| 66 | #expect(MarkupPreview.format(forFile: "profile/README.org") == "org") | |
| 67 | } | |
| 68 | ||
| 69 | @Test func markdownFilesRenderAsMarkdown() { | |
| 70 | #expect(MarkupPreview.format(forFile: "README.md") == "md") | |
| 71 | #expect(MarkupPreview.format(forFile: "notes.markdown") == "md") | |
| 72 | #expect(MarkupPreview.format(forFile: "CHANGES.MD") == "md") | |
| 73 | } | |
| 74 | ||
| 75 | /// Source files show as source, so there is nothing to preview. | |
| 76 | @Test func otherFilesHaveNoPreview() { | |
| 77 | #expect(MarkupPreview.format(forFile: "main.swift") == nil) | |
| 78 | #expect(MarkupPreview.format(forFile: "Makefile") == nil) | |
| 79 | #expect(MarkupPreview.format(forFile: "notes.txt") == nil) | |
| 80 | } | |
| 81 | } | |
| 82 | ``` | |
| 83 | ||
| 84 | - [ ] **Step 3: Run the unit target to verify it fails** | |
| 85 | ||
| 86 | Expected: build FAILS with `cannot find 'MarkupPreview' in scope`. | |
| 87 | ||
| 88 | - [ ] **Step 4: Create the view** | |
| 89 | ||
| 90 | Create `gitbay/Views/Shared/MarkupPreview.swift`: | |
| 91 | ||
| 92 | ```swift | |
| 93 | import SwiftUI | |
| 94 | ||
| 95 | /// A collapsed "Preview" row under a markup editor. Expanded, it renders | |
| 96 | /// the draft with `FormattedBodyView`, the view the app's threads use, so | |
| 97 | /// what it shows is what the app will show once the text is sent. It sits | |
| 98 | /// under the editor rather than replacing it, so the draft stays in view. | |
| 99 | struct MarkupPreview: View { | |
| 100 | ||
| 101 | let text: String | |
| 102 | let format: String? | |
| 103 | let identifier: String | |
| 104 | ||
| 105 | @State private var expanded = false | |
| 106 | ||
| 107 | var body: some View { | |
| 108 | DisclosureGroup("Preview", isExpanded: $expanded) { | |
| 109 | if text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { | |
| 110 | Text("Nothing to preview.") | |
| 111 | .font(.gbSans(.subheadline)) | |
| 112 | .foregroundStyle(.secondary) | |
| 113 | } else { | |
| 114 | FormattedBodyView(text: text, format: format) | |
| 115 | .padding(.vertical, 4) | |
| 116 | } | |
| 117 | } | |
| 118 | .font(.gbSans(.subheadline)) | |
| 119 | .accessibilityIdentifier(identifier) | |
| 120 | } | |
| 121 | ||
| 122 | /// The format a file's contents render in, or nil for a file shown as | |
| 123 | /// source. The same extensions the forge renders a README or wiki | |
| 124 | /// page from. | |
| 125 | static func format(forFile name: String) -> String? { | |
| 126 | switch (name as NSString).pathExtension.lowercased() { | |
| 127 | case "org": "org" | |
| 128 | case "md", "markdown": "md" | |
| 129 | default: nil | |
| 130 | } | |
| 131 | } | |
| 132 | } | |
| 133 | ``` | |
| 134 | ||
| 135 | - [ ] **Step 5: Run the unit target to verify it passes** | |
| 136 | ||
| 137 | Expected: `Passed <BASE + 3> 0`. | |
| 138 | ||
| 139 | - [ ] **Step 6: Commit** | |
| 140 | ||
| 141 | ```bash | |
| 142 | cd /Users/cmc/git/krz/gitbay-ios && git add gitbay/Views/Shared/MarkupPreview.swift gitbayTests/MarkupPreviewTests.swift && git commit -m "views: MarkupPreview, a draft rendered as the thread renders it" | |
| 143 | ``` | |
| 144 | ||
| 145 | --- | |
| 146 | ||
| 147 | ### Task 2: Put it under every markup editor | |
| 148 | ||
| 149 | **Files:** | |
| 150 | - Modify: `gitbay/Views/Shared/ComposeSheet.swift:63-75` (Body section) | |
| 151 | - Modify: `gitbay/Views/MRs/MRListView.swift:114-125` (`MRCreateSheet` Body section) | |
| 152 | - Modify: `gitbay/Views/Issues/IssueView.swift:203-226` (comment row) | |
| 153 | - Modify: `gitbay/Views/MRs/MRView.swift:538-561` (comment row) | |
| 154 | - Modify: `gitbay/Views/Releases/ReleaseListView.swift:106-110` (`Notes` section) | |
| 155 | - Modify: `gitbay/Views/Repos/FileEditSheet.swift:18-28` (content section) | |
| 156 | ||
| 157 | **Interfaces:** | |
| 158 | - Consumes: `MarkupPreview(text:format:identifier:)`, `MarkupPreview.format(forFile:)` (Task 1); `FileViewModel.fileName: String`. | |
| 159 | - Produces: accessibility identifiers `compose-preview`, `mr-create-preview`, `issue-comment-field`, `issue-comment-preview`, `mr-comment-field`, `mr-comment-preview`, `release-notes-preview`, `file-edit-preview`. | |
| 160 | ||
| 161 | Line numbers are as of `main` after 1.4.0; find each block by its content. | |
| 162 | ||
| 163 | - [ ] **Step 1: Issue and MR create/edit, release edit** | |
| 164 | ||
| 165 | In `gitbay/Views/Shared/ComposeSheet.swift`, in the `Section("Body")`, after the `if showsFormat { Picker("Format", …) … }` block, add: | |
| 166 | ||
| 167 | ```swift | |
| 168 | MarkupPreview(text: bodyText, format: format, identifier: "compose-preview") | |
| 169 | ``` | |
| 170 | ||
| 171 | `format` is the constant `"md"` where the sheet shows no picker, which is what the release editor sends. | |
| 172 | ||
| 173 | - [ ] **Step 2: MR create** | |
| 174 | ||
| 175 | In `gitbay/Views/MRs/MRListView.swift`, in `MRCreateSheet`'s `Section("Body")`, after the `Picker("Format", selection: $format) { … }` block with its modifiers, add: | |
| 176 | ||
| 177 | ```swift | |
| 178 | MarkupPreview(text: bodyText, format: format, identifier: "mr-create-preview") | |
| 179 | ``` | |
| 180 | ||
| 181 | - [ ] **Step 3: Issue comments** | |
| 182 | ||
| 183 | In `gitbay/Views/Issues/IssueView.swift`, in the comment `HStack`, give the field an identifier: change | |
| 184 | ||
| 185 | ```swift | |
| 186 | TextField("Comment", text: $commentText, axis: .vertical) | |
| 187 | .lineLimit(1...5) | |
| 188 | ``` | |
| 189 | ||
| 190 | to | |
| 191 | ||
| 192 | ```swift | |
| 193 | TextField("Comment", text: $commentText, axis: .vertical) | |
| 194 | .lineLimit(1...5) | |
| 195 | .accessibilityIdentifier("issue-comment-field") | |
| 196 | ``` | |
| 197 | ||
| 198 | and immediately after the closing brace of that `HStack`, add: | |
| 199 | ||
| 200 | ```swift | |
| 201 | if !commentText.isEmpty { | |
| 202 | MarkupPreview(text: commentText, format: commentFormat, | |
| 203 | identifier: "issue-comment-preview") | |
| 204 | } | |
| 205 | ``` | |
| 206 | ||
| 207 | - [ ] **Step 4: MR comments** | |
| 208 | ||
| 209 | In `gitbay/Views/MRs/MRView.swift`, make the same two changes to the comment `HStack` with the identifiers `mr-comment-field` and `mr-comment-preview`: | |
| 210 | ||
| 211 | ```swift | |
| 212 | TextField("Comment", text: $commentText, axis: .vertical) | |
| 213 | .lineLimit(1...5) | |
| 214 | .accessibilityIdentifier("mr-comment-field") | |
| 215 | ``` | |
| 216 | ||
| 217 | ```swift | |
| 218 | if !commentText.isEmpty { | |
| 219 | MarkupPreview(text: commentText, format: commentFormat, | |
| 220 | identifier: "mr-comment-preview") | |
| 221 | } | |
| 222 | ``` | |
| 223 | ||
| 224 | - [ ] **Step 5: Release notes on create** | |
| 225 | ||
| 226 | In `gitbay/Views/Releases/ReleaseListView.swift`, replace | |
| 227 | ||
| 228 | ```swift | |
| 229 | Section("Notes") { | |
| 230 | TextEditor(text: $notes) | |
| 231 | .frame(minHeight: 140) | |
| 232 | .autocorrectionDisabled() | |
| 233 | } | |
| 234 | ``` | |
| 235 | ||
| 236 | with | |
| 237 | ||
| 238 | ```swift | |
| 239 | Section("Notes") { | |
| 240 | TextEditor(text: $notes) | |
| 241 | .frame(minHeight: 140) | |
| 242 | .autocorrectionDisabled() | |
| 243 | // release create sends no --format, and release notes | |
| 244 | // render as markdown. | |
| 245 | MarkupPreview(text: notes, format: "md", identifier: "release-notes-preview") | |
| 246 | } | |
| 247 | ``` | |
| 248 | ||
| 249 | - [ ] **Step 6: Markup files in the file editor** | |
| 250 | ||
| 251 | In `gitbay/Views/Repos/FileEditSheet.swift`, after the `Section { TextEditor(text: $content) … } header: { … } footer: { … }` block and before `Section("Message")`, add: | |
| 252 | ||
| 253 | ```swift | |
| 254 | if let format = MarkupPreview.format(forFile: model.fileName) { | |
| 255 | Section { | |
| 256 | MarkupPreview(text: content, format: format, identifier: "file-edit-preview") | |
| 257 | } | |
| 258 | } | |
| 259 | ``` | |
| 260 | ||
| 261 | - [ ] **Step 7: Build and run the unit target** | |
| 262 | ||
| 263 | Expected: `Passed <BASE + 3> 0`. The call sites have no unit tests; Task 3's live step drives one end to end. | |
| 264 | ||
| 265 | - [ ] **Step 8: Check the design greps** | |
| 266 | ||
| 267 | ```bash | |
| 268 | cd /Users/cmc/git/krz/gitbay-ios && grep -rnE '\.(green|red|blue|orange|yellow|purple)\b' gitbay/ --include=*.swift | grep -v gb; \ | |
| 269 | grep -rn '\.font(\.' gitbay/Views/Shared/MarkupPreview.swift | grep -vE 'gbSans|gbMono' | |
| 270 | ``` | |
| 271 | ||
| 272 | Expected: no output from either. | |
| 273 | ||
| 274 | - [ ] **Step 9: Commit** | |
| 275 | ||
| 276 | ```bash | |
| 277 | cd /Users/cmc/git/krz/gitbay-ios && git add gitbay/Views/Shared/ComposeSheet.swift gitbay/Views/MRs/MRListView.swift gitbay/Views/Issues/IssueView.swift gitbay/Views/MRs/MRView.swift gitbay/Views/Releases/ReleaseListView.swift gitbay/Views/Repos/FileEditSheet.swift && git commit -m "Preview under every markup editor | |
| 278 | ||
| 279 | Issue and MR bodies on create and edit, their comments, release notes, | |
| 280 | and a .md, .markdown or .org file in the file editor." | |
| 281 | ``` | |
| 282 | ||
| 283 | --- | |
| 284 | ||
| 285 | ### Task 3: Live step, then the MR | |
| 286 | ||
| 287 | **Files:** | |
| 288 | - Modify: `gitbayUITests/LiveSmokeUITests.swift` (new `testMarkupPreviewFlow()` and its `setUpWithError` guard) | |
| 289 | ||
| 290 | **Interfaces:** | |
| 291 | - Consumes: `Self.fixtureRepo`, `Self.fixtureIssueTitle`, `requireFixtureRepo()`, `ensureFixtureIssue()`, `openRepo(_:)`, `scrollTo(_:swipes:)`; identifiers `issue-comment-field`, `issue-comment-preview` (Task 2). | |
| 292 | ||
| 293 | The test types a comment and never sends it. | |
| 294 | ||
| 295 | - [ ] **Step 1: Add the fixture guard** | |
| 296 | ||
| 297 | In `setUpWithError()`, after the last `if name.contains(…)` block, add: | |
| 298 | ||
| 299 | ```swift | |
| 300 | if name.contains("testMarkupPreviewFlow") { | |
| 301 | try requireFixtureRepo() | |
| 302 | ensureFixtureIssue() | |
| 303 | } | |
| 304 | ``` | |
| 305 | ||
| 306 | - [ ] **Step 2: Add the test** | |
| 307 | ||
| 308 | Add after the last test in the class: | |
| 309 | ||
| 310 | ```swift | |
| 311 | /// A comment's preview renders its markup before anything is sent: | |
| 312 | /// the bold markers are gone and the words remain. Nothing is posted. | |
| 313 | func testMarkupPreviewFlow() throws { | |
| 314 | openRepo(Self.fixtureRepo) | |
| 315 | app.staticTexts["Issues"].firstMatch.tap() | |
| 316 | let issue = app.staticTexts[Self.fixtureIssueTitle].firstMatch | |
| 317 | XCTAssertTrue(issue.waitForExistence(timeout: 15), "fixture issue not listed") | |
| 318 | issue.tap() | |
| 319 | ||
| 320 | let field = app.descendants(matching: .any) | |
| 321 | .matching(identifier: "issue-comment-field").firstMatch | |
| 322 | XCTAssertTrue(scrollTo(field, swipes: 10), "no comment field on the issue") | |
| 323 | field.tap() | |
| 324 | let draft = "**ui-smoke preview**" | |
| 325 | field.typeText(draft) | |
| 326 | ||
| 327 | let preview = app.descendants(matching: .any) | |
| 328 | .matching(identifier: "issue-comment-preview").firstMatch | |
| 329 | XCTAssertTrue(scrollTo(preview), "no preview row for a non-empty comment") | |
| 330 | preview.tap() | |
| 331 | XCTAssertTrue(app.staticTexts | |
| 332 | .containing(NSPredicate(format: "label CONTAINS 'ui-smoke preview'")).firstMatch | |
| 333 | .waitForExistence(timeout: 5), "preview did not render the draft") | |
| 334 | XCTAssertFalse(app.staticTexts | |
| 335 | .containing(NSPredicate(format: "label CONTAINS '**ui-smoke'")).firstMatch.exists, | |
| 336 | "preview shows the raw markup") | |
| 337 | ||
| 338 | // Leave the fixture as found: clear the draft, send nothing. | |
| 339 | XCTAssertTrue(scrollTo(field), "comment field scrolled away") | |
| 340 | field.tap() | |
| 341 | field.typeText(String(repeating: XCUIKeyboardKey.delete.rawValue, count: draft.count)) | |
| 342 | XCTAssertFalse(preview.exists, "preview row outlived an empty comment") | |
| 343 | } | |
| 344 | ``` | |
| 345 | ||
| 346 | - [ ] **Step 3: Build the UI test target** | |
| 347 | ||
| 348 | ```bash | |
| 349 | cd /Users/cmc/git/krz/gitbay-ios && xcodebuild -project gitbay.xcodeproj -scheme gitbay \ | |
| 350 | -destination 'id=0A03DF15-0F45-4726-8080-DBFE919F46B6' build-for-testing 2>&1 | grep -E "error:|TEST BUILD (SUCCEEDED|FAILED)" | tail -5 | |
| 351 | ``` | |
| 352 | ||
| 353 | Expected: `** TEST BUILD SUCCEEDED **`. | |
| 354 | ||
| 355 | - [ ] **Step 4: Run the live test (needs a token from the user)** | |
| 356 | ||
| 357 | Ask the user for an `ios-smoke` token (`ssh -F /dev/null -i ~/.ssh/gitbay-ios-smoke -o IdentitiesOnly=yes git@gitbay.org token create --name ui-smoke --scope full --ttl 1d`). | |
| 358 | ||
| 359 | ```bash | |
| 360 | cd /Users/cmc/git/krz/gitbay-ios && TEST_RUNNER_GITBAY_UITEST_LIVE=1 TEST_RUNNER_GITBAY_UITEST_TOKEN="$TOKEN" \ | |
| 361 | xcodebuild -project gitbay.xcodeproj -scheme gitbay -destination 'id=0A03DF15-0F45-4726-8080-DBFE919F46B6' \ | |
| 362 | -only-testing:gitbayUITests/LiveSmokeUITests/testMarkupPreviewFlow \ | |
| 363 | -resultBundlePath /tmp/gb-live.xcresult test 2>&1 | grep -E "error:|passed|failed|TEST (SUCCEEDED|FAILED)" | tail -10 | |
| 364 | ``` | |
| 365 | ||
| 366 | Expected: `passed`. If the markdown renderer splits the bold run into a separate element whose label is exactly `ui-smoke preview`, the `CONTAINS` predicate still matches. | |
| 367 | ||
| 368 | - [ ] **Step 5: Look at one preview in the simulator** | |
| 369 | ||
| 370 | Launch the app on the iPhone 17 Pro simulator, open any issue's New Issue sheet, type a heading and a list in Org format, expand Preview, and screenshot it in light and dark. Confirm it uses the same type and colours as a rendered comment in a thread. Record the result in the MR body. | |
| 371 | ||
| 372 | - [ ] **Step 6: Commit, push, open the MR** | |
| 373 | ||
| 374 | ```bash | |
| 375 | cd /Users/cmc/git/krz/gitbay-ios && git add gitbayUITests/LiveSmokeUITests.swift && git commit -m "live suite: a comment preview renders its markup" && \ | |
| 376 | git push -u origin markup-preview && gitbay mr create --source markup-preview --target main \ | |
| 377 | --title "Preview markup before sending" --file - <<'EOF' | |
| 378 | A collapsed Preview row under every markup editor: issue and MR create | |
| 379 | and edit, their comments, release create and edit, and a .md, | |
| 380 | .markdown or .org file in the file editor. It renders the draft with | |
| 381 | FormattedBodyView, the view the app's threads use, so it shows what the | |
| 382 | app will show. Like the threads, it resolves no autolinks; the web's | |
| 383 | preview does. | |
| 384 | ||
| 385 | Spec: docs/superpowers/specs/2026-09-22-parity-followup-design.md | |
| 386 | EOF | |
| 387 | ``` | |
| 388 | ||
| 389 | --- | |
| 390 | ||
| 391 | ### Task 4: Bump to 1.5.0 (14) | |
| 392 | ||
| 393 | Starts only after the user has merged the MR from Task 3. | |
| 394 | ||
| 395 | **Files:** | |
| 396 | - Modify: `gitbay.xcodeproj/project.pbxproj` (the four version lines) | |
| 397 | ||
| 398 | - [ ] **Step 1: Branch, bump, verify** | |
| 399 | ||
| 400 | ```bash | |
| 401 | cd /Users/cmc/git/krz/gitbay-ios && git switch main && git pull --ff-only && git switch -c bump-1.5.0 && \ | |
| 402 | sed -i '' -e 's/MARKETING_VERSION = 1.4.0;/MARKETING_VERSION = 1.5.0;/' \ | |
| 403 | -e 's/CURRENT_PROJECT_VERSION = 13;/CURRENT_PROJECT_VERSION = 14;/' gitbay.xcodeproj/project.pbxproj && \ | |
| 404 | git diff --stat && plutil -lint gitbay.xcodeproj/project.pbxproj | |
| 405 | ``` | |
| 406 | ||
| 407 | Expected: `1 file changed, 4 insertions(+), 4 deletions(-)` and `OK`. | |
| 408 | ||
| 409 | - [ ] **Step 2: Both runtimes and a Release device build** | |
| 410 | ||
| 411 | Run the unit target on iPhone 17 Pro (26.5), then: | |
| 412 | ||
| 413 | ```bash | |
| 414 | cd /Users/cmc/git/krz/gitbay-ios && xcodebuild -project gitbay.xcodeproj -scheme gitbay \ | |
| 415 | -destination 'id=9F8E5D84-0A15-4C64-8A45-994667A73817' -only-testing:gitbayTests test 2>&1 | grep -E "TEST (SUCCEEDED|FAILED)" | |
| 416 | xcodebuild build -scheme gitbay -configuration Release -destination 'generic/platform=iOS' CODE_SIGNING_ALLOWED=NO 2>&1 \ | |
| 417 | | grep -E "error:|While running pass|BUILD (SUCCEEDED|FAILED)" | tail -5 | |
| 418 | ``` | |
| 419 | ||
| 420 | Expected: `** TEST SUCCEEDED **` and `** BUILD SUCCEEDED **`. | |
| 421 | ||
| 422 | - [ ] **Step 3: Commit, push, open the MR** | |
| 423 | ||
| 424 | ```bash | |
| 425 | cd /Users/cmc/git/krz/gitbay-ios && git add gitbay.xcodeproj/project.pbxproj && git commit -m "Bump to 1.5.0 (14)" && \ | |
| 426 | git push -u origin bump-1.5.0 && gitbay mr create --source bump-1.5.0 --target main --title "Bump to 1.5.0 (14)" --body "Markup previews." | |
| 427 | ``` | |
| 428 | ||
| 429 | Tagging `v1.5.0`, archiving and uploading are the user's. | |
| 430 | ||
| 431 | - [ ] **Step 4: Flip the Parity rows upstream** | |
| 432 | ||
| 433 | Once 1.5.0 is released, in `~/git/krz/gitbay` on a new branch `parity-ios-1.5.0`, set the iOS column to `yes` for `preview body markup` (Merge requests and Issues), `preview an edited markup file` and `preview release notes`. Commit, push, and open the MR titled "Parity: iOS 1.5.0 rows". | |