summaryrefslogtreecommitdiff
path: root/Hutch/Views/Tickets/TicketListViewModel.swift
Commit message (Collapse)AuthorAgeFilesLines
* Phase 1: close the write gaps (#3)v3.6.0Christian Cleberg2026-07-151-0/+88
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | * refactor: collapse duplicated request paths in SRHTClient Five request paths each repeated the token guard, header setup, status-code handling, and a ~35-line #if DEBUG logging block. The file carried that block five times over. Extract makeAuthorizedRequest, send, and encodedGraphQLBody, and route execute, executeAndCache, executeMultipartFiles, and performGraphQLRequest through them. executeMultipart is now the single-file case of executeMultipartFiles, which it already was byte for byte. 938 lines to 612, with one copy of the logging block. fetchText keeps its own guard: it is a GET to an allowlisted URL and must not run GraphQL error checks over what is usually a plain-text build log. One behavior change falls out. executeAndCache wrote the raw response to the cache before decoding, so a 200 carrying GraphQL errors was cached and then thrown. Routing it through performGraphQLRequest surfaces those errors first, so error payloads are no longer cached. * feat: edit and delete tickets updateTicket and deleteTicket both existed in todo.sr.ht's API but were never called, so a ticket could be filed and its status changed but its subject and body were frozen from the moment it was created, and it could never be removed. Edit opens a sheet seeded with the current subject and body. The input carries only fields that actually changed, so an edit cannot clobber a field the user did not touch, and Save stays disabled until something differs. Clearing the body sends an explicit null via updateValue rather than a nil subscript assignment, which would drop the key and silently leave the old body in place — the same trap fixed for repository descriptions in 7ffef07. Delete is destructive and irreversible, so it sits behind a confirmation dialog naming the ticket and pops the detail view on success. * feat: subscribe to and unsubscribe from tickets ticketSubscribe and ticketUnsubscribe existed in the API but were never called, so email notifications for a ticket could only be managed on the web. Ticket.subscription is null when the user is not subscribed, so the detail query now reads it and the menu reflects real server state rather than guessing. The toggle updates optimistically and reverts on failure, so the control never claims a subscription that did not take. Decoded into the private payload rather than TicketDetail, which is Codable and cached — adding a field there would have changed the cached shape and touched every optimistic-update construction site. * feat: subscribe to and unsubscribe from trackers trackerSubscribe and trackerUnsubscribe existed in the API but were never called. Tracker.subscription is null when not subscribed, so the state can be read rather than guessed. The read is a separate uncached query. The tickets query it sits beside is paginated and cached, and a per-user subscription has no business riding along in page payloads or being served stale from disk. Unsubscribe passes tickets: false, so leaving a tracker does not silently drop subscriptions to individual tickets the user opted into. * feat: unsubscribe from mailing lists mailingListUnsubscribe existed in the API but was never called, so the list of subscriptions was readable and nothing more. Scoped to unsubscribe. MailingList has no subscription field, unlike Ticket and Tracker, so per-list state is only knowable from the subscriptions query — which is exactly what builds this view. Subscribing would need a list the user is by definition not subscribed to, and sr.ht exposes no discovery API to find one (see SCOPE.md on hub.sr.ht), so there is nowhere honest to put that action yet. The row is removed optimistically and restored if the mutation fails. The confirmation says plainly that Hutch cannot resubscribe, since it cannot. * feat: manage todo and lists email preferences updatePreferences existed on both services but was never called, so these were web-only settings. The two services expose preferences/updatePreferences under identical names but with different fields — notifySelf on todo, copySelf on lists — and there is no shared preferences service, so both are read and written side by side. They load concurrently and one service being unreachable does not hide the other's toggle. These are server-side and apply beyond Hutch, unlike the @AppStorage toggles above them in Settings, so the footer says so and each toggle reverts if its mutation fails. * refactor: drop the memory-only cache path Two executeCached overloads existed with different return types and semantics: one doing stale-while-revalidate against the persistent cache with TTLs, the other only consulting the in-memory responseCache. The second was an easy thing to reach for by mistake, since the compiler picked it purely on argument labels. It turned out to be dead. All 38 call sites already used the TTL-aware overload, and the memory-only one was the sole caller of executeAndCache, so both are removed. Its doc comment promised refresh "via the onRefresh callback", which the signature has not had for some time. SRHTClient is now 569 lines, down from 938 before this branch. responseCache stays as the in-memory layer behind cachedPayload and the three view models that read it directly. * chore: bump to 3.6.0 and record Phase 1 MARKETING_VERSION 3.5.0 -> 3.6.0, build 87 -> 88. * fix: decode preferences responses on the main actor The module sets SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor, so the response types are implicitly main-actor isolated and their Decodable conformances are too. Decoding straight from an `async let` used those conformances from a nonisolated context, which warns today and is an error in the Swift 6 language mode. Move each fetch into its own method and `async let` over those instead, so decoding stays on the main actor. This is what HomeViewModel.loadDashboard already does, and the concurrency is unaffected — the network work still overlaps, since execute suspends and frees the actor.
* feat(cache): persist read-only API responsesv3.3.1Christian Cleberg2026-05-061-7/+36
| | | | | | | | | | | | Add a bounded stale-while-revalidate cache at the Sourcehut API boundary with stable keys, centralized TTLs, request coalescing, payload hashing, and LRU disk pruning. Cache high-value read-only repo, build, ticket, project, profile, paste, and Home/Work Queue data while keeping mutations network-only and invalidating related prefixes after successful writes. Add focused cache tests and implementation notes.
* fix(tickets): load all pages on tracker open for accurate Open tabChristian Cleberg2026-04-131-5/+26
| | | | | | | | todo.sr.ht only exposes cursor-based `tracker.tickets` (no status filter), so client-side Open filtering missed older open tickets when only the first page was loaded. Paginate until the cursor is exhausted in loadTickets(). Fixes: https://todo.sr.ht/~ccleberg/hutch/62
* fix: suppress false "network request failed" error on ticket list scrollChristian Cleberg2026-04-131-1/+5
| | | | | | | | | | | | | | When scrolling through a large tracker, SwiftUI's .task modifier cancels in-flight pagination requests as rows leave the screen. URLSession responds with URLError(-999), which was being surfaced to the user as "The network request failed. Please try again." — even though the cancellation is expected and benign. Guard against Task.isCancelled in the catch block so cancellations are silently ignored; isLoadingMore is still reset unconditionally so the next scroll attempt retries from the same cursor position. Fixes: https://todo.sr.ht/~ccleberg/hutch/61
* fix: show empty state for bare git repos instead of an errorChristian Cleberg2026-04-131-7/+27
| | | | | | | | | | | git.sr.ht returns "internal system error" from the references query and "reference not found" from the log/readme queries when a repo has no commits. Broaden isEmptyRepositoryError to cover missingReference, unknownRevision, noRows, notFound, and those two message strings, then apply the same silent-empty treatment to loadReferences and loadArtifacts (which previously surfaced any error directly to the user). Fixes: https://todo.sr.ht/~ccleberg/hutch/60
* perf: cache filtered collections and add equatable row viewsChristian Cleberg2026-04-131-5/+15
| | | | | | | | | | | | | | | | Convert filteredJobs, filteredPastes, and filteredTickets from computed properties to stored properties updated via didSet on their inputs. Each update function guards with an equality check before assigning, so @Observable skips the notification when the filtered result hasn't changed — preventing list re-renders on auto-refresh when no visible data has actually changed. Add Equatable conformance to BuildRowView, PasteRowView, TicketRowView, and SelectableTicketRow, and apply .equatable() at each ForEach call site. When a list does re-render, SwiftUI now skips body evaluation for rows whose model value is identical to the previous pass. Implements: https://todo.sr.ht/~ccleberg/hutch/57
* feat: add search and recent queriesChristian Cleberg2026-04-121-3/+35
| | | | | Implements: https://todo.sr.ht/~ccleberg/hutch/47 Implements: https://todo.sr.ht/~ccleberg/hutch/48
* feat: add ticket label management and bulk actionsChristian Cleberg2026-04-121-1/+258
| | | | | Implements: https://todo.sr.ht/~ccleberg/hutch/45 Implements: https://todo.sr.ht/~ccleberg/hutch/46
* feat: add ticket saved filters and label filteringChristian Cleberg2026-04-121-29/+149
| | | | | Implements: https://todo.sr.ht/~ccleberg/hutch/32 Implements: https://todo.sr.ht/~ccleberg/hutch/33
* Add tracker management (delete/update, ACLs, labels)v2.7.0Christian Cleberg2026-04-011-39/+21
| | | | | | | | | | | | | - Add swipe actions (delete/edit) to tracker list - Add tracker settings menu on detail screen - Implement updateTracker and deleteTracker mutations - Implement ACL management (updateUserACL, updateTrackerACL, deleteACL) - Implement label management (createLabel, updateLabel, deleteLabel) - Add dedicated UI flows for ACLs and labels - Ensure state refresh after mutations - Handle loading, errors, and destructive confirmations Implements: https://todo.sr.ht/~ccleberg/Hutch/12
* feat: swipe actions on tickets, pastes, builds, and home dashboardChristian Cleberg2026-03-221-0/+305
|
* feat: add search to builds, trackers, tickets, pastes, mailing lists, and inboxChristian Cleberg2026-03-221-5/+75
|
* v2.1: bundled polish and fixesChristian Cleberg2026-03-191-3/+3
|
* v1.0Christian Cleberg2026-03-171-0/+215