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". | ||