Commit 84533fa664

84533fa6642dea4528eb3a0c9ee5f54fcae25b31

parent: c108ce3805

Verified · cmc

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

Build log: jump to failure opens at the failed step's start

It scrolled to the failed step's last line, which is the end of the log
whenever the last step fails, so both buttons went to the same place.

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}