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 {
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}