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 {
243243 isOpenByDefault(section, in: detail) != toggled.contains(section.n)
244244 }
245245
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.
247248 private func scroll(to target: Jump, in detail: BuildDetail, proxy: ScrollViewProxy) {
248249 let log = detail.lines
249250 let index = target == .failure ? log.failed : log.sections.indices.last
@@ -251,10 +252,17 @@ struct BuildDetailView: View {
251252 let section = log.sections[index]
252253 query = ""
253254 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
255259 // Let the opened section lay out before scrolling into it.
256260 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 }
258266 }
259267 }
260268}