Commit c619fe4a96

c619fe4a96bd0af63b1f8777ca8f9f2dea227e86

parent: 55f362e4c3

Unsigned

cmc <hello@cleberg.net> · 2026-04-02 04:37 UTC

feat: show commit dates in Refs tab

Layout: unified · split

Hutch.xcodeproj/project.pbxproj +8 −8
@@ -539,7 +539,7 @@
539 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 539 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
540 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; 540 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
541 CODE_SIGN_STYLE = Automatic; 541 CODE_SIGN_STYLE = Automatic;
542 CURRENT_PROJECT_VERSION = 19; 542 CURRENT_PROJECT_VERSION = 20;
543 DEVELOPMENT_TEAM = ZCNAX3VL9D; 543 DEVELOPMENT_TEAM = ZCNAX3VL9D;
544 ENABLE_PREVIEWS = YES; 544 ENABLE_PREVIEWS = YES;
545 GENERATE_INFOPLIST_FILE = YES; 545 GENERATE_INFOPLIST_FILE = YES;
@@ -556,7 +556,7 @@
556 "$(inherited)", 556 "$(inherited)",
557 "@executable_path/Frameworks", 557 "@executable_path/Frameworks",
558 ); 558 );
559 MARKETING_VERSION = 2.8.0; 559 MARKETING_VERSION = 2.8.1;
560 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; 560 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
561 PRODUCT_NAME = "$(TARGET_NAME)"; 561 PRODUCT_NAME = "$(TARGET_NAME)";
562 STRING_CATALOG_GENERATE_SYMBOLS = YES; 562 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -576,7 +576,7 @@
576 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 576 ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
577 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements; 577 CODE_SIGN_ENTITLEMENTS = Hutch/Hutch.entitlements;
578 CODE_SIGN_STYLE = Automatic; 578 CODE_SIGN_STYLE = Automatic;
579 CURRENT_PROJECT_VERSION = 19; 579 CURRENT_PROJECT_VERSION = 20;
580 DEVELOPMENT_TEAM = ZCNAX3VL9D; 580 DEVELOPMENT_TEAM = ZCNAX3VL9D;
581 ENABLE_PREVIEWS = YES; 581 ENABLE_PREVIEWS = YES;
582 GENERATE_INFOPLIST_FILE = YES; 582 GENERATE_INFOPLIST_FILE = YES;
@@ -593,7 +593,7 @@
593 "$(inherited)", 593 "$(inherited)",
594 "@executable_path/Frameworks", 594 "@executable_path/Frameworks",
595 ); 595 );
596 MARKETING_VERSION = 2.8.0; 596 MARKETING_VERSION = 2.8.1;
597 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch; 597 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch;
598 PRODUCT_NAME = "$(TARGET_NAME)"; 598 PRODUCT_NAME = "$(TARGET_NAME)";
599 STRING_CATALOG_GENERATE_SYMBOLS = YES; 599 STRING_CATALOG_GENERATE_SYMBOLS = YES;
@@ -656,7 +656,7 @@
656 APPLICATION_EXTENSION_API_ONLY = YES; 656 APPLICATION_EXTENSION_API_ONLY = YES;
657 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; 657 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
658 CODE_SIGN_STYLE = Automatic; 658 CODE_SIGN_STYLE = Automatic;
659 CURRENT_PROJECT_VERSION = 19; 659 CURRENT_PROJECT_VERSION = 20;
660 DEVELOPMENT_TEAM = ZCNAX3VL9D; 660 DEVELOPMENT_TEAM = ZCNAX3VL9D;
661 GENERATE_INFOPLIST_FILE = NO; 661 GENERATE_INFOPLIST_FILE = NO;
662 INFOPLIST_FILE = HutchWidgetExtension/Info.plist; 662 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -666,7 +666,7 @@
666 "@executable_path/Frameworks", 666 "@executable_path/Frameworks",
667 "@executable_path/../../Frameworks", 667 "@executable_path/../../Frameworks",
668 ); 668 );
669 MARKETING_VERSION = 2.8.0; 669 MARKETING_VERSION = 2.8.1;
670 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; 670 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
671 PRODUCT_NAME = "$(TARGET_NAME)"; 671 PRODUCT_NAME = "$(TARGET_NAME)";
672 SKIP_INSTALL = YES; 672 SKIP_INSTALL = YES;
@@ -685,7 +685,7 @@
685 APPLICATION_EXTENSION_API_ONLY = YES; 685 APPLICATION_EXTENSION_API_ONLY = YES;
686 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements; 686 CODE_SIGN_ENTITLEMENTS = HutchWidgetExtension/HutchWidgetExtension.entitlements;
687 CODE_SIGN_STYLE = Automatic; 687 CODE_SIGN_STYLE = Automatic;
688 CURRENT_PROJECT_VERSION = 19; 688 CURRENT_PROJECT_VERSION = 20;
689 DEVELOPMENT_TEAM = ZCNAX3VL9D; 689 DEVELOPMENT_TEAM = ZCNAX3VL9D;
690 GENERATE_INFOPLIST_FILE = NO; 690 GENERATE_INFOPLIST_FILE = NO;
691 INFOPLIST_FILE = HutchWidgetExtension/Info.plist; 691 INFOPLIST_FILE = HutchWidgetExtension/Info.plist;
@@ -695,7 +695,7 @@
695 "@executable_path/Frameworks", 695 "@executable_path/Frameworks",
696 "@executable_path/../../Frameworks", 696 "@executable_path/../../Frameworks",
697 ); 697 );
698 MARKETING_VERSION = 2.8.0; 698 MARKETING_VERSION = 2.8.1;
699 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension; 699 PRODUCT_BUNDLE_IDENTIFIER = net.cleberg.Hutch.HutchWidgetExtension;
700 PRODUCT_NAME = "$(TARGET_NAME)"; 700 PRODUCT_NAME = "$(TARGET_NAME)";
701 SKIP_INSTALL = YES; 701 SKIP_INSTALL = YES;
Hutch/Models/Git.swift +7
@@ -82,6 +82,13 @@ struct Reference: Codable, Sendable, Hashable {
82 let target: String? 82 let target: String?
83} 83}
84 84
85/// A Git reference enriched with the date of its tip commit or tag, for display in the Refs tab.
86struct ReferenceDetail: Sendable, Hashable {
87 let name: String
88 let target: String?
89 let date: Date?
90}
91
85// MARK: - TreeEntry 92// MARK: - TreeEntry
86 93
87/// An entry in a Git tree (file or directory). 94/// An entry in a Git tree (file or directory).
Hutch/Views/Repositories/ReferencesListView.swift +10 −3
@@ -51,14 +51,21 @@ struct ReferencesListView: View {
51} 51}
52 52
53private struct ReferenceRow: View { 53private struct ReferenceRow: View {
54 let reference: Reference 54 let reference: ReferenceDetail
55 let prefix: String 55 let prefix: String
56 56
57 var body: some View { 57 var body: some View {
58 HStack { 58 HStack {
59 Label { 59 Label {
60 Text(shortName) 60 VStack(alignment: .leading, spacing: 2) {
61 .font(.body.monospaced()) 61 Text(shortName)
62 .font(.body.monospaced())
63 if let date = reference.date {
64 Text(date.relativeDescription)
65 .font(.caption)
66 .foregroundStyle(.secondary)
67 }
68 }
62 } icon: { 69 } icon: {
63 Image(systemName: icon) 70 Image(systemName: icon)
64 .foregroundStyle(iconColor) 71 .foregroundStyle(iconColor)
Hutch/Views/Repositories/RepositoryDetailViewModel.swift +36 −6
@@ -24,10 +24,33 @@ private struct RefsRepository: Decodable, Sendable {
24} 24}
25 25
26private struct RefsPage: Decodable, Sendable { 26private struct RefsPage: Decodable, Sendable {
27 let results: [Reference] 27 let results: [ReferencePayload]
28 let cursor: String? 28 let cursor: String?
29} 29}
30 30
31/// Decodes a single reference including the optional follow object for date extraction.
32private struct ReferencePayload: Decodable, Sendable {
33 let name: String
34 let target: String?
35 let follow: ReferenceFollow?
36
37 func toDetail() -> ReferenceDetail {
38 // Branches and lightweight tags follow to a Commit (author.time).
39 // Annotated tags follow to a Tag object (tagger.time).
40 let date = follow?.author?.time ?? follow?.tagger?.time
41 return ReferenceDetail(name: name, target: target, date: date)
42 }
43}
44
45private struct ReferenceFollow: Decodable, Sendable {
46 let author: ReferenceSignature?
47 let tagger: ReferenceSignature?
48}
49
50private struct ReferenceSignature: Decodable, Sendable {
51 let time: Date
52}
53
31private struct ReadmeResponse: Decodable, Sendable { 54private struct ReadmeResponse: Decodable, Sendable {
32 let repository: ReadmeRepository? 55 let repository: ReadmeRepository?
33} 56}
@@ -98,8 +121,8 @@ final class RepositoryDetailViewModel {
98 121
99 // MARK: - References state 122 // MARK: - References state
100 123
101 private(set) var branches: [Reference] = [] 124 private(set) var branches: [ReferenceDetail] = []
102 private(set) var tags: [Reference] = [] 125 private(set) var tags: [ReferenceDetail] = []
103 private(set) var isLoadingRefs = false 126 private(set) var isLoadingRefs = false
104 127
105 // MARK: - README state 128 // MARK: - README state
@@ -220,7 +243,14 @@ final class RepositoryDetailViewModel {
220 query refs($rid: ID!) { 243 query refs($rid: ID!) {
221 repository(rid: $rid) { 244 repository(rid: $rid) {
222 references { 245 references {
223 results { name target } 246 results {
247 name
248 target
249 follow {
250 ... on Commit { author { time } }
251 ... on Tag { tagger { time } }
252 }
253 }
224 cursor 254 cursor
225 } 255 }
226 } 256 }
@@ -240,8 +270,8 @@ final class RepositoryDetailViewModel {
240 responseType: RefsResponse.self 270 responseType: RefsResponse.self
241 ) 271 )
242 let allRefs = result.repository?.references.results ?? [] 272 let allRefs = result.repository?.references.results ?? []
243 branches = allRefs.filter { $0.name.hasPrefix("refs/heads/") } 273 branches = allRefs.filter { $0.name.hasPrefix("refs/heads/") }.map { $0.toDetail() }
244 tags = allRefs.filter { $0.name.hasPrefix("refs/tags/") } 274 tags = allRefs.filter { $0.name.hasPrefix("refs/tags/") }.map { $0.toDetail() }
245 } catch { 275 } catch {
246 self.error = error.userFacingMessage 276 self.error = error.userFacingMessage
247 } 277 }
Hutch/Views/Repositories/RepositorySettingsView.swift +1 −1
@@ -2,7 +2,7 @@ import SwiftUI
2 2
3struct RepositorySettingsView: View { 3struct RepositorySettingsView: View {
4 let repository: RepositorySummary 4 let repository: RepositorySummary
5 let branches: [Reference] 5 let branches: [ReferenceDetail]
6 let client: SRHTClient 6 let client: SRHTClient
7 let onRenamed: (String) -> Void 7 let onRenamed: (String) -> Void
8 let onDeleted: () -> Void 8 let onDeleted: () -> Void
Hutch/Views/Repositories/RepositorySettingsViewModel.swift +2 −2
@@ -102,7 +102,7 @@ final class RepositorySettingsViewModel {
102 102
103 // MARK: - Branches (for HEAD picker) 103 // MARK: - Branches (for HEAD picker)
104 104
105 var branches: [Reference] 105 var branches: [ReferenceDetail]
106 106
107 // MARK: - Results 107 // MARK: - Results
108 108
@@ -112,7 +112,7 @@ final class RepositorySettingsViewModel {
112 112
113 init( 113 init(
114 repository: RepositorySummary, 114 repository: RepositorySummary,
115 branches: [Reference], 115 branches: [ReferenceDetail],
116 client: SRHTClient 116 client: SRHTClient
117 ) { 117 ) {
118 self.repositoryId = repository.id 118 self.repositoryId = repository.id
codex-fix-refs-type-conflict.md added +89
@@ -0,0 +1,89 @@
1# Fix: Conflicting `[Reference]` vs `[ReferenceDetail]` in RepositorySettingsView
2
3## Background
4
5`RepositoryDetailViewModel.branches` was recently changed from `[Reference]` to
6`[ReferenceDetail]` to support displaying commit dates in the Refs tab.
7`ReferenceDetail` has the same `name: String` and `target: String?` fields as
8`Reference`, plus an additional `date: Date?`.
9
10`RepositorySettingsView` and `RepositorySettingsViewModel` still declare their
11`branches` parameter as `[Reference]`, causing this compiler error:
12
13```
14RepositoryDetailView.swift:56:51
15Conflicting arguments to generic parameter 'T' ('[ReferenceDetail]' vs. '[Reference]')
16```
17
18The settings view model only uses `branches` for the HEAD branch picker
19(`selectedHeadReferenceForSave()` accesses `$0.name`). No mapping back to
20`Reference` is needed — just update the type throughout the settings layer.
21
22---
23
24## Changes Required
25
26### 1. `Hutch/Views/Repositories/RepositorySettingsViewModel.swift`
27
28**Change the stored property type:**
29
30Find:
31```swift
32var branches: [Reference]
33```
34
35Replace with:
36```swift
37var branches: [ReferenceDetail]
38```
39
40**Change the `init` parameter type:**
41
42Find:
43```swift
44init(
45 repository: RepositorySummary,
46 branches: [Reference],
47 client: SRHTClient
48) {
49```
50
51Replace with:
52```swift
53init(
54 repository: RepositorySummary,
55 branches: [ReferenceDetail],
56 client: SRHTClient
57) {
58```
59
60### 2. `Hutch/Views/Repositories/RepositorySettingsView.swift`
61
62**Change the stored property type:**
63
64Find:
65```swift
66let branches: [Reference]
67```
68
69Replace with:
70```swift
71let branches: [ReferenceDetail]
72```
73
74---
75
76## No Other Changes
77
78- Do not modify `Reference` or `ReferenceDetail` in `Git.swift`.
79- Do not modify `RepositoryDetailView.swift` — the call site is already correct.
80- Do not modify `ReferencesListView.swift` or `RepositoryDetailViewModel.swift`.
81- `selectedHeadReferenceForSave()` in the settings view model accesses only
82 `$0.name` on each branch, which exists on `ReferenceDetail` — no logic
83 changes are needed.
84
85## Verification
86
87Build the project. The compiler error at `RepositoryDetailView.swift:56` should
88be gone. Confirm the repository settings sheet still opens and the HEAD branch
89picker populates correctly.