Commit 0ef480e4cc

0ef480e4cc770c96b9a43586d3ae33e5f265da94

parent: c108ce3805

Unsigned

cmc <hello@cleberg.net> · 2026-09-30 03:58 UTC

Build log: jump to failure opens at the failed step's start (!149)

"Jump to failure" scrolled to the failed step's last line, the same place as "Jump to end" whenever the last step fails. It now brings the failed step's header to the top so its output reads from the start. "Jump to end" is unchanged. 573 unit tests, Release build.

Layout: unified · split

gitbay/Views/Builds/BuildDetailView.swift +11 −3
@@ -243,7 +243,8 @@ struct BuildDetailView: View {
243 isOpenByDefault(section, in: detail) != toggled.contains(section.n) 243 isOpenByDefault(section, in: detail) != toggled.contains(section.n)
244 } 244 }
245 245
246 /// To the end of the failed step, where its error is, or of the log. 246 /// To the top of the failed step, so its output reads from the start,
247 /// or to the last line of the log.
247 private func scroll(to target: Jump, in detail: BuildDetail, proxy: ScrollViewProxy) { 248 private func scroll(to target: Jump, in detail: BuildDetail, proxy: ScrollViewProxy) {
248 let log = detail.lines 249 let log = detail.lines
249 let index = target == .failure ? log.failed : log.sections.indices.last 250 let index = target == .failure ? log.failed : log.sections.indices.last
@@ -251,10 +252,17 @@ struct BuildDetailView: View {
251 let section = log.sections[index] 252 let section = log.sections[index]
252 query = "" 253 query = ""
253 if !isOpen(section, in: detail) { toggled.formSymmetricDifference([section.n]) } 254 if !isOpen(section, in: detail) { toggled.formSymmetricDifference([section.n]) }
254 let anchor: AnyHashable = section.lines.last.map { AnyHashable($0.id) } ?? AnyHashable("step-\(section.n)") 255 // An unstepped log has no headers; its top is the first line.
256 let step: AnyHashable = log.stepped || section.lines.isEmpty
257 ? AnyHashable("step-\(section.n)") : AnyHashable(section.lines[0].id)
258 let last: AnyHashable = section.lines.last.map { AnyHashable($0.id) } ?? step
255 // Let the opened section lay out before scrolling into it. 259 // Let the opened section lay out before scrolling into it.
256 Task { @MainActor in 260 Task { @MainActor in
257 proxy.scrollTo(anchor, anchor: .bottom) 261 if target == .failure {
262 proxy.scrollTo(step, anchor: .top)
263 } else {
264 proxy.scrollTo(last, anchor: .bottom)
265 }
258 } 266 }
259 } 267 }
260} 268}