iOS client for Grain grain.social
ios photography atproto
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore: signpost story comment sheet + loadComments

Adds os_signpost coverage for the comment sheet lifecycle
(onAppear, onDisappear with last detent, detent changes, task
begin/end) and the StoryCommentsViewModel loadComments interval.

Gives the Zed log:StoryComments stream the events needed to
correlate sheet-lifecycle issues with fetch timing.

+30 -2
+9 -1
Grain/ViewModels/StoryCommentsViewModel.swift
··· 2 2 import os 3 3 4 4 private let logger = Logger(subsystem: "social.grain.grain", category: "StoryComments") 5 + private let scvmSignposter = OSSignposter(subsystem: "social.grain.grain", category: "StoryComments") 5 6 6 7 @Observable 7 8 @MainActor ··· 83 84 // MARK: - Full Comment Loading 84 85 85 86 func loadComments(storyUri: String, auth: AuthContext? = nil) async { 86 - guard !isLoading else { return } 87 + guard !isLoading else { 88 + scvmSignposter.emitEvent("loadComments.skipped", "reason=already-loading") 89 + return 90 + } 91 + let state = scvmSignposter.beginInterval("loadComments", "uri=\(storyUri)") 87 92 isLoading = true 88 93 commentCursor = nil 89 94 hasMoreComments = true ··· 101 106 if storyUri == activeStoryUri { 102 107 latestComment = latest 103 108 } 109 + let count = response.comments.count 110 + scvmSignposter.endInterval("loadComments", state, "count=\(count)") 104 111 } catch { 105 112 logger.error("Failed to load comments: \(error)") 113 + scvmSignposter.endInterval("loadComments", state, "error") 106 114 } 107 115 isLoading = false 108 116 }
+21 -1
Grain/Views/Stories/StoryCommentSheet.swift
··· 1 + import os 1 2 import SwiftUI 3 + 4 + private let scsLogger = Logger(subsystem: "social.grain.grain", category: "StoryCommentSheet") 5 + private let scsSignposter = OSSignposter(subsystem: "social.grain.grain", category: "StoryCommentSheet") 2 6 3 7 struct StoryCommentSheet: View { 4 8 @Environment(AuthManager.self) private var auth ··· 10 14 var focusInput: Bool = false 11 15 var onProfileTap: ((String) -> Void)? 12 16 var onDismiss: (() -> Void)? 17 + 18 + @State private var selectedDetent: PresentationDetent = .medium 13 19 14 20 var body: some View { 15 21 CommentSheetContent( ··· 29 35 dismissStyle: .done, 30 36 focusOnAppear: focusInput 31 37 ) 32 - .presentationDetents([.medium, .large]) 38 + .presentationDetents([.medium, .large], selection: $selectedDetent) 39 + .onAppear { 40 + scsLogger.info("[onAppear] uri=\(storyUri) focusInput=\(focusInput)") 41 + scsSignposter.emitEvent("sheet.onAppear", "focusInput=\(focusInput)") 42 + } 43 + .onDisappear { 44 + scsLogger.info("[onDisappear] uri=\(storyUri) lastDetent=\(String(describing: selectedDetent))") 45 + scsSignposter.emitEvent("sheet.onDisappear", "detent=\(String(describing: selectedDetent))") 46 + } 47 + .onChange(of: selectedDetent) { _, newValue in 48 + scsLogger.info("[detent.change] now=\(String(describing: newValue))") 49 + scsSignposter.emitEvent("detent.change", "detent=\(String(describing: newValue))") 50 + } 33 51 .task { 52 + scsSignposter.emitEvent("task.load.begin") 34 53 await viewModel.loadComments(storyUri: storyUri, auth: auth.authContext()) 54 + scsSignposter.emitEvent("task.load.end") 35 55 } 36 56 } 37 57 }