Commit c619fe4a96
Unsigned
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. | ||
| 86 | struct 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 | ||
| 53 | private struct ReferenceRow: View { | 53 | private 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 | ||
| 26 | private struct RefsPage: Decodable, Sendable { | 26 | private 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. | ||
| 32 | private 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 | |||
| 45 | private struct ReferenceFollow: Decodable, Sendable { | ||
| 46 | let author: ReferenceSignature? | ||
| 47 | let tagger: ReferenceSignature? | ||
| 48 | } | ||
| 49 | |||
| 50 | private struct ReferenceSignature: Decodable, Sendable { | ||
| 51 | let time: Date | ||
| 52 | } | ||
| 53 | |||
| 31 | private struct ReadmeResponse: Decodable, Sendable { | 54 | private 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 | ||
| 3 | struct RepositorySettingsView: View { | 3 | struct 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 | ``` | ||
| 14 | RepositoryDetailView.swift:56:51 | ||
| 15 | Conflicting arguments to generic parameter 'T' ('[ReferenceDetail]' vs. '[Reference]') | ||
| 16 | ``` | ||
| 17 | |||
| 18 | The 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 | |||
| 30 | Find: | ||
| 31 | ```swift | ||
| 32 | var branches: [Reference] | ||
| 33 | ``` | ||
| 34 | |||
| 35 | Replace with: | ||
| 36 | ```swift | ||
| 37 | var branches: [ReferenceDetail] | ||
| 38 | ``` | ||
| 39 | |||
| 40 | **Change the `init` parameter type:** | ||
| 41 | |||
| 42 | Find: | ||
| 43 | ```swift | ||
| 44 | init( | ||
| 45 | repository: RepositorySummary, | ||
| 46 | branches: [Reference], | ||
| 47 | client: SRHTClient | ||
| 48 | ) { | ||
| 49 | ``` | ||
| 50 | |||
| 51 | Replace with: | ||
| 52 | ```swift | ||
| 53 | init( | ||
| 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 | |||
| 64 | Find: | ||
| 65 | ```swift | ||
| 66 | let branches: [Reference] | ||
| 67 | ``` | ||
| 68 | |||
| 69 | Replace with: | ||
| 70 | ```swift | ||
| 71 | let 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 | |||
| 87 | Build the project. The compiler error at `RepositoryDetailView.swift:56` should | ||
| 88 | be gone. Confirm the repository settings sheet still opens and the HEAD branch | ||
| 89 | picker populates correctly. | ||