Commit b1fc78b637

b1fc78b637b27ae2642cd722014ca3bbf3a917f3

parent: f4f3e8781b

Unsigned

cmc <hello@cleberg.net> · 2026-09-07 05:35 UTC

Edit opens on the format the body was stored in (!61)

An org body's Edit sheet opened on Markdown. `ComposeSheet` stored the format as `Binding<String>?`, which is not a dynamic property, so the sheet body never re-ran after the binding took the stored format. Now a real `@Binding` plus a `showsFormat` flag; the release call site is unchanged.

Reproduced and verified in the simulator with the real `IssueView` against a stubbed `issue show` returning `body_format: org`. The live smoke edit flow gains an org round-trip on the fixture issue.

Layout: unified · split

gitbay/Views/Shared/ComposeSheet.swift +23 −5
@@ -9,11 +9,29 @@ struct ComposeSheet: View {
9 let errorMessage: String? 9 let errorMessage: String?
10 @Binding var title: String 10 @Binding var title: String
11 @Binding var bodyText: String 11 @Binding var bodyText: String
12 /// nil hides the picker entirely — the release editor has no parity 12 /// Held as a real `@Binding`, not `Binding<String>?`: an optional
13 /// row and passes nothing, leaving that call site untouched. 13 /// binding is not a dynamic property, so the sheet body never re-ran
14 var format: Binding<String>? = nil 14 /// when the value changed and the picker kept whatever it first saw.
15 @Binding var format: String
16 let showsFormat: Bool
15 let onSubmit: () -> Void 17 let onSubmit: () -> Void
16 18
19 /// A nil `format` hides the picker entirely — the release editor has
20 /// no parity row and passes nothing, leaving that call site untouched.
21 init(heading: String, submitLabel: String, working: Bool, errorMessage: String?,
22 title: Binding<String>, bodyText: Binding<String>,
23 format: Binding<String>? = nil, onSubmit: @escaping () -> Void) {
24 self.heading = heading
25 self.submitLabel = submitLabel
26 self.working = working
27 self.errorMessage = errorMessage
28 _title = title
29 _bodyText = bodyText
30 _format = format ?? .constant("md")
31 showsFormat = format != nil
32 self.onSubmit = onSubmit
33 }
34
17 @Environment(\.dismiss) private var dismiss 35 @Environment(\.dismiss) private var dismiss
18 36
19 var body: some View { 37 var body: some View {
@@ -33,8 +51,8 @@ struct ComposeSheet: View {
33 .font(.gbSans(.body)) 51 .font(.gbSans(.body))
34 .autocorrectionDisabled() 52 .autocorrectionDisabled()
35 .accessibilityIdentifier("compose-body") 53 .accessibilityIdentifier("compose-body")
36 if let format { 54 if showsFormat {
37 Picker("Format", selection: format) { 55 Picker("Format", selection: $format) {
38 Text("Markdown").tag("md") 56 Text("Markdown").tag("md")
39 Text("Org").tag("org") 57 Text("Org").tag("org")
40 } 58 }
gitbayUITests/LiveSmokeUITests.swift +18
@@ -188,11 +188,29 @@ final class LiveSmokeUITests: XCTestCase {
188 XCTAssertTrue(title.waitForExistence(timeout: 5), "edit sheet did not open") 188 XCTAssertTrue(title.waitForExistence(timeout: 5), "edit sheet did not open")
189 XCTAssertTrue((title.value as? String)?.contains("ui-smoke fixture") == true, 189 XCTAssertTrue((title.value as? String)?.contains("ui-smoke fixture") == true,
190 "edit sheet did not prefill") 190 "edit sheet did not prefill")
191 // Save the body as org, reopen, and the picker must start on Org:
192 // the sheet has to read the stored format, not its own default.
193 let format = app.segmentedControls.matching(identifier: "compose-format").firstMatch
194 XCTAssertTrue(format.waitForExistence(timeout: 5), "format picker missing")
195 XCTAssertTrue(format.buttons["Markdown"].isSelected, "fixture did not open on Markdown")
196 format.buttons["Org"].tap()
191 app.descendants(matching: .any).matching(identifier: "compose-submit") 197 app.descendants(matching: .any).matching(identifier: "compose-submit")
192 .firstMatch.tap() 198 .firstMatch.tap()
193 // Sheet dismissal proves the save round-tripped without error. 199 // Sheet dismissal proves the save round-tripped without error.
194 XCTAssertTrue(waitForDisappearance(title, timeout: 15), "edit save did not dismiss") 200 XCTAssertTrue(waitForDisappearance(title, timeout: 15), "edit save did not dismiss")
195 201
202 app.descendants(matching: .any).matching(identifier: "issue-actions-menu")
203 .firstMatch.tap()
204 XCTAssertTrue(edit.waitForExistence(timeout: 5))
205 edit.tap()
206 XCTAssertTrue(format.waitForExistence(timeout: 5), "edit sheet did not reopen")
207 XCTAssertTrue(format.buttons["Org"].isSelected, "edit sheet did not open on the stored format")
208 // Restore the fixture to markdown.
209 format.buttons["Markdown"].tap()
210 app.descendants(matching: .any).matching(identifier: "compose-submit")
211 .firstMatch.tap()
212 XCTAssertTrue(waitForDisappearance(title, timeout: 15), "edit save did not dismiss")
213
196 back() // to issues list 214 back() // to issues list
197 back() // to repo screen 215 back() // to repo screen
198 216