Commit af86fd7326

af86fd73262a0a9eaa27899b5b920d344b784890

parent: f4f3e8781b

Verified · cmc

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

Edit opens on the format the body was stored in

ComposeSheet held its format as `Binding<String>?`. An optional binding
is not a dynamic property, so the sheet body never re-ran when the value
changed: the segmented picker kept whatever the binding held when the
body first evaluated, which was the "md" default, while the binding
itself had already moved to the stored format. Editing an org body
therefore opened on Markdown. Title and body were unaffected because
they were real `@Binding`s.

The format is now a `@Binding` with a `showsFormat` flag; the release
editor still passes nothing and shows no picker.

The live smoke edit flow now saves the fixture as org, reopens it, and
asserts the picker starts on Org before restoring markdown.

Layout: unified · split

gitbay/Views/Shared/ComposeSheet.swift +23 −5
@@ -9,11 +9,29 @@ struct ComposeSheet: View {
99 let errorMessage: String?
1010 @Binding var title: String
1111 @Binding var bodyText: String
12 /// nil hides the picker entirely — the release editor has no parity
13 /// row and passes nothing, leaving that call site untouched.
14 var format: Binding<String>? = nil
12 /// Held as a real `@Binding`, not `Binding<String>?`: an optional
13 /// binding is not a dynamic property, so the sheet body never re-ran
14 /// when the value changed and the picker kept whatever it first saw.
15 @Binding var format: String
16 let showsFormat: Bool
1517 let onSubmit: () -> Void
1618
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
1735 @Environment(\.dismiss) private var dismiss
1836
1937 var body: some View {
@@ -33,8 +51,8 @@ struct ComposeSheet: View {
3351 .font(.gbSans(.body))
3452 .autocorrectionDisabled()
3553 .accessibilityIdentifier("compose-body")
36 if let format {
37 Picker("Format", selection: format) {
54 if showsFormat {
55 Picker("Format", selection: $format) {
3856 Text("Markdown").tag("md")
3957 Text("Org").tag("org")
4058 }
gitbayUITests/LiveSmokeUITests.swift +18
@@ -188,11 +188,29 @@ final class LiveSmokeUITests: XCTestCase {
188188 XCTAssertTrue(title.waitForExistence(timeout: 5), "edit sheet did not open")
189189 XCTAssertTrue((title.value as? String)?.contains("ui-smoke fixture") == true,
190190 "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()
191197 app.descendants(matching: .any).matching(identifier: "compose-submit")
192198 .firstMatch.tap()
193199 // Sheet dismissal proves the save round-tripped without error.
194200 XCTAssertTrue(waitForDisappearance(title, timeout: 15), "edit save did not dismiss")
195201
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
196214 back() // to issues list
197215 back() // to repo screen
198216