Commit 2dd2805e4a

2dd2805e4ae00226dd9cfb9ec9b7c916e88a6fca

parent: 57e588172b

Unsigned

cmc <hello@cleberg.net> · 2026-04-01 19:04 UTC

Fix several bugs ahead of 2.6.1

FileTreeView: when wrap is OFF, activate an explicit width constraint on
the code container derived from the widest line, giving the horizontal
scroll view a defined content size. Previously the constraint was
deactivated with nothing to replace it, making horizontal scrolling
unreliable. Also set horizontalScrollView.isScrollEnabled = !wrapLines
to prevent gesture conflicts when wrap is ON.

InboxView: remove the unnecessary loadThreads() network call fired after
markAllThreadsRead(). The threads array is already updated optimistically
and read state is persisted via InboxReadStateStore, so the refetch was
redundant and caused a brief loading flash.

HomeView: add scenePhase onChange handler to refresh dashboard data when
the app returns to the foreground, matching the existing pattern in
InboxView.

DiffView: wrap DiffBlockView content in a horizontal ScrollView so long
diff lines are readable. Switch inner VStack to LazyVStack for better
performance on large diffs.

SettingsView: replace direct UserDefaults access in behaviorSection with
@AppStorage, consistent with the rest of the app.

Layout: unified · split

Hutch/Views/Home/HomeView.swift +7
@@ -3,6 +3,7 @@ import SwiftUI
33struct HomeView: View {
44 @AppStorage(AppStorageKeys.swipeActionsEnabled) private var swipeActionsEnabled = true
55 @Environment(AppState.self) private var appState
6 @Environment(\.scenePhase) private var scenePhase
67 @State private var viewModel: HomeViewModel?
78 private let previewLimit = 4
89 private let projectPreviewLimit = 3
@@ -39,6 +40,12 @@ struct HomeView: View {
3940
4041 await vm.loadDashboard()
4142 }
43 .onChange(of: scenePhase) { _, newPhase in
44 guard newPhase == .active, let viewModel else { return }
45 Task {
46 await viewModel.loadDashboard()
47 }
48 }
4249 }
4350
4451 @ViewBuilder
Hutch/Views/Inbox/InboxView.swift −3
@@ -70,9 +70,6 @@ struct InboxView: View {
7070 withAnimation(.easeInOut(duration: 0.2)) {
7171 viewModel.markAllThreadsRead()
7272 }
73 Task {
74 await viewModel.loadThreads()
75 }
7673 }
7774 }
7875 }
Hutch/Views/Repositories/DiffView.swift +6 −4
@@ -76,13 +76,15 @@ private struct DiffBlockView: View {
7676 let lines: [String]
7777
7878 var body: some View {
79 VStack(alignment: .leading, spacing: 0) {
80 ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
81 DiffLineView(line: line)
79 ScrollView(.horizontal, showsIndicators: false) {
80 LazyVStack(alignment: .leading, spacing: 0) {
81 ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
82 DiffLineView(line: line)
83 }
8284 }
85 .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
8386 }
8487 .font(.system(.caption, design: .monospaced))
85 .frame(maxWidth: .infinity, alignment: .leading)
8688 .background(Color(.secondarySystemBackground))
8789 }
8890}
Hutch/Views/Repositories/FileTreeView.swift +17 −1
@@ -441,6 +441,7 @@ private final class CodeFileUIView: UIView {
441441 private let codeStackView = UIStackView()
442442
443443 private var codeContainerWidthConstraint: NSLayoutConstraint?
444 private var codeContainerExplicitWidthConstraint: NSLayoutConstraint?
444445 private var gutterWidthConstraint: NSLayoutConstraint?
445446
446447 private var rows: [LineRow] = []
@@ -524,6 +525,7 @@ private final class CodeFileUIView: UIView {
524525 codeContainerView.addSubview(codeStackView)
525526
526527 codeContainerWidthConstraint = codeContainerView.widthAnchor.constraint(equalTo: horizontalScrollView.frameLayoutGuide.widthAnchor)
528 codeContainerExplicitWidthConstraint = codeContainerView.widthAnchor.constraint(equalToConstant: 0)
527529 gutterWidthConstraint = gutterContainerView.widthAnchor.constraint(equalToConstant: 0)
528530
529531 NSLayoutConstraint.activate([
@@ -627,12 +629,14 @@ private final class CodeFileUIView: UIView {
627629
628630 private func updateWrapConfiguration(resetHorizontalOffset: Bool) {
629631 horizontalScrollView.alwaysBounceHorizontal = !wrapLines
630 horizontalScrollView.isScrollEnabled = true
632 horizontalScrollView.isScrollEnabled = !wrapLines
631633
632634 if wrapLines {
633635 codeContainerWidthConstraint?.isActive = true
636 codeContainerExplicitWidthConstraint?.isActive = false
634637 } else {
635638 codeContainerWidthConstraint?.isActive = false
639 codeContainerExplicitWidthConstraint?.isActive = true
636640 }
637641
638642 for row in rows {
@@ -664,6 +668,18 @@ private final class CodeFileUIView: UIView {
664668 row.codeHeightConstraint.constant = rowHeight
665669 }
666670
671 if wrapLines {
672 codeContainerExplicitWidthConstraint?.constant = 0
673 } else {
674 let maxLineWidth = rows.reduce(CGFloat(0)) { partialResult, row in
675 let measuredWidth = row.codeLabel.sizeThatFits(
676 CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
677 ).width
678 return max(partialResult, ceil(measuredWidth))
679 }
680 codeContainerExplicitWidthConstraint?.constant = max(maxLineWidth, availableWidth)
681 }
682
667683 needsLineLayoutUpdate = false
668684 }
669685
Hutch/Views/Settings/SettingsView.swift +2 −9
@@ -8,6 +8,7 @@ private let settingsBioMarkdownOptions = AttributedString.MarkdownParsingOptions
88struct SettingsView: View {
99 @Environment(AppState.self) private var appState
1010 @Environment(\.colorScheme) private var colorScheme
11 @AppStorage(AppStorageKeys.swipeActionsEnabled) private var swipeActionsEnabled = true
1112 @State private var viewModel: SettingsViewModel?
1213 @State private var pendingDestructiveAction: SettingsDestructiveAction?
1314
@@ -389,15 +390,7 @@ struct SettingsView: View {
389390 @ViewBuilder
390391 private func behaviorSection() -> some View {
391392 Section {
392 Toggle(
393 "Swipe actions",
394 isOn: Binding(
395 get: {
396 UserDefaults.standard.object(forKey: AppStorageKeys.swipeActionsEnabled) as? Bool ?? true
397 },
398 set: { UserDefaults.standard.set($0, forKey: AppStorageKeys.swipeActionsEnabled) }
399 )
400 )
393 Toggle("Swipe actions", isOn: $swipeActionsEnabled)
401394 } header: {
402395 Text("Behavior")
403396 } footer: {