Commit 6a8dd2561e

6a8dd2561ef660be7dccbe6d95e7ecd06c996051

parent: e5f5db3bba

Unsigned

cmc <hello@cleberg.net> · 2026-04-03 04:21 UTC

fix: remove stray files

Layout: unified · split

codex-fix-refs-type-conflict.md deleted −89
@@ -1,89 +0,0 @@
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.
codex-user-resource-browsing.md deleted −249
@@ -1,249 +0,0 @@
1# feat: User resource browsing from profile
2
3## Goal
4
5Extend `UserProfileView` so that after looking up a user, their public
6repositories and trackers are shown as browsable sections below the existing
7profile metadata. This mirrors the sr.ht `~username` page.
8
9---
10
11## Context
12
13**Entry point:** `Hutch/Views/Lookup/LookupView.swift`
14Looking up a user opens `UserProfileView` in a sheet. Currently the view only
15shows static metadata fields from the `User` model.
16
17**Owner identifier:** `user.canonicalName` (e.g. `~username`). Strip the leading
18`~` when passing to GraphQL `username` parameters — see the existing pattern in
19`AppState.resolveRepository(owner:name:)`.
20
21**Existing row views to reuse:**
22- `RepositoryRowView` in `Hutch/Views/Repositories/RepositoryRowView.swift`
23- Tracker row style from `TrackerListView` private `TrackerRowView`
24
25**API pattern for user-scoped queries** (from `AppState.swift`):
26```graphql
27query repoLookup($owner: String!, $name: String!) {
28 user(username: $owner) {
29 repository(name: $name) { ... }
30 }
31}
32```
33The same `user(username:)` root field supports `repositories` and `trackers`
34paginated collections on git.sr.ht and todo.sr.ht respectively.
35
36---
37
38## New File: `Hutch/Views/Lookup/UserProfileViewModel.swift`
39
40Create an `@Observable @MainActor` view model following the same pattern as
41`RepositoryListViewModel` and `TrackerListViewModel`.
42
43```swift
44@Observable
45@MainActor
46final class UserProfileViewModel {
47 private(set) var repositories: [RepositorySummary] = []
48 private(set) var trackers: [TrackerSummary] = []
49 private(set) var isLoadingRepositories = false
50 private(set) var isLoadingTrackers = false
51 var repositoriesError: String?
52 var trackersError: String?
53
54 private let client: SRHTClient
55 let ownerUsername: String // without leading ~
56
57 init(ownerUsername: String, client: SRHTClient) { ... }
58
59 func loadRepositories() async { ... }
60 func loadTrackers() async { ... }
61}
62```
63
64**GraphQL queries:**
65
66Repositories (execute against `.git` service):
67```graphql
68query userRepositories($owner: String!) {
69 user(username: $owner) {
70 repositories {
71 results {
72 id rid name description visibility updated
73 owner { canonicalName }
74 HEAD { name target }
75 }
76 cursor
77 }
78 }
79}
80```
81
82Trackers (execute against `.todo` service):
83```graphql
84query userTrackers($owner: String!) {
85 user(username: $owner) {
86 trackers {
87 results {
88 id rid name description visibility updated
89 owner { canonicalName }
90 }
91 cursor
92 }
93 }
94}
95```
96
97Decode using private response structs identical to those in
98`RepositoryListViewModel` and `TrackerListViewModel`. Map results to
99`RepositorySummary` and `TrackerSummary` exactly as those view models do.
100
101---
102
103## Modified File: `Hutch/Views/Lookup/UserProfileView.swift`
104
105### View model instantiation
106
107Add `@State private var profileViewModel: UserProfileViewModel?` and initialise
108it in `.task` using `user.canonicalName` with the leading `~` stripped:
109
110```swift
111.task {
112 let owner = user.canonicalName.hasPrefix("~")
113 ? String(user.canonicalName.dropFirst())
114 : user.canonicalName
115 let vm = UserProfileViewModel(ownerUsername: owner, client: appState.client)
116 profileViewModel = vm
117 async let repos: () = vm.loadRepositories()
118 async let trackers: () = vm.loadTrackers()
119 _ = await (repos, trackers)
120}
121```
122
123Restore `@Environment(AppState.self) private var appState` (it was removed in a
124recent commit but is needed for `client` access).
125
126### Repositories section
127
128Add after the existing metadata sections:
129
130```swift
131Section {
132 if viewModel.isLoadingRepositories && viewModel.repositories.isEmpty {
133 ProgressView()
134 } else if viewModel.repositories.isEmpty {
135 Text("No public repositories.")
136 .foregroundStyle(.secondary)
137 } else {
138 ForEach(viewModel.repositories.prefix(4)) { repo in
139 NavigationLink {
140 RepositoryDetailView(repository: repo)
141 } label: {
142 RepositoryRowView(repository: repo, buildStatus: .none)
143 }
144 }
145 if viewModel.repositories.count > 4 {
146 NavigationLink("See All") {
147 UserRepositoriesView(viewModel: viewModel)
148 }
149 }
150 }
151} header: {
152 Text("Repositories")
153}
154```
155
156### Trackers section
157
158Immediately after the Repositories section:
159
160```swift
161Section {
162 if viewModel.isLoadingTrackers && viewModel.trackers.isEmpty {
163 ProgressView()
164 } else if viewModel.trackers.isEmpty {
165 Text("No public trackers.")
166 .foregroundStyle(.secondary)
167 } else {
168 ForEach(viewModel.trackers.prefix(4)) { tracker in
169 NavigationLink {
170 TicketListView(tracker: tracker)
171 } label: {
172 TrackerRowView(tracker: tracker)
173 }
174 }
175 if viewModel.trackers.count > 4 {
176 NavigationLink("See All") {
177 UserTrackersView(viewModel: viewModel)
178 }
179 }
180 }
181} header: {
182 Text("Trackers")
183}
184```
185
186`TrackerRowView` — use the same VStack layout as the private `TrackerRowView`
187in `TrackerListView.swift`. Define it as a private struct in
188`UserProfileView.swift` rather than duplicating from `TrackerListView`.
189
190---
191
192## New File: `Hutch/Views/Lookup/UserRepositoriesView.swift`
193
194A simple full-list view for "See All" repositories:
195
196```swift
197struct UserRepositoriesView: View {
198 let viewModel: UserProfileViewModel
199
200 var body: some View {
201 List {
202 ForEach(viewModel.repositories) { repo in
203 NavigationLink {
204 RepositoryDetailView(repository: repo)
205 } label: {
206 RepositoryRowView(repository: repo, buildStatus: .none)
207 }
208 }
209 }
210 .listStyle(.plain)
211 .navigationTitle("Repositories")
212 .navigationBarTitleDisplayMode(.inline)
213 .overlay {
214 if viewModel.isLoadingRepositories && viewModel.repositories.isEmpty {
215 SRHTLoadingStateView(message: "Loading repositories…")
216 }
217 }
218 .refreshable {
219 await viewModel.loadRepositories()
220 }
221 }
222}
223```
224
225## New File: `Hutch/Views/Lookup/UserTrackersView.swift`
226
227Same pattern as `UserRepositoriesView` but for trackers, navigating to
228`TicketListView(tracker:)`.
229
230---
231
232## Navigation context
233
234`UserProfileView` is always presented inside a `NavigationStack` via the sheet
235in `LookupView`. `NavigationLink` destinations push correctly within that stack.
236No changes to `LookupView` or `MoreRoute` are needed.
237
238---
239
240## Verification
241
2421. Look up a user with public repositories and trackers. Confirm both sections
243 appear below the profile metadata.
2442. Tap a repository row — confirm `RepositoryDetailView` pushes correctly.
2453. Tap a tracker row — confirm `TicketListView` pushes correctly.
2464. For users with more than 4 items, confirm "See All" pushes the full list.
2475. Look up a user with no public resources — confirm the empty-state text
248 renders in each section rather than crashing.
2496. Build with no warnings.