Compare commits

...

10 Commits

Author SHA1 Message Date
Dean Wenstrand
81565b7676 Add pastDueUsers, newPaidToday, and cohort paidTotal to usage stats
All checks were successful
Tests / Tests (pull_request) Successful in 4m48s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:28:53 -04:00
Dean Wenstrand
722b2ca2b0 Re-add legacy shot-sim operations alongside GetShotLabVideos
All checks were successful
Tests / Tests (pull_request) Successful in 1m37s
Mobile master's shot-simulation-screen still uses LiveStreamPicker and
RecordedShotPicker (GetLiveStreams / GetRecordedStreams) while ALSO
using the video-first GetShotLabVideos picker — a transitional state.
1a95439 replaced the old operations in-place, so no gql commit carried
the union and mobile master's pointer could not satisfy its own code.
live_streams.gql is restored verbatim; GetRecordedStreams is re-added
in legacy_shotsim.gql so master's recorded_shots.gql (which now holds
GetShotLabVideos) stays untouched. Drop both when the old pickers are
deleted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 22:57:28 -07:00
79f38fa45f Merge pull request 'dean/nested-comment-replies' (#307) from dean/nested-comment-replies into master
Reviewed-on: #307
2026-07-23 03:24:38 +00:00
Dean Wenstrand
16684f0057 Fetch replyToCommentId in comment reply selections
All checks were successful
Tests / Tests (pull_request) Successful in 12s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 20:06:28 -07:00
Dean Wenstrand
6a599d7d2b Add replyToCommentId to CommentGQL
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:59:43 -07:00
2886d2f5b8 Merge pull request 'Select stream.id in upload-status operations so the cache stays normalized' (#306) from dean/upload-stream-id-fields into master
Reviewed-on: #306
2026-07-23 02:38:07 +00:00
Dean Wenstrand
8441bb12e9 Select stream.id in upload-status operations so the cache stays normalized
All checks were successful
Tests / Tests (pull_request) Successful in 11s
UploadStreamWithDetails and the editUploadStream response selected
stream fields without id. Apollo cannot normalize an object with no id
selected, so every upload-service poll rewrote VideoGQL.stream as an
anonymous inline object — clobbering the id that feed/detail queries
select. Their cache reads went partial and useQuery silently fell back
to the last network result, rendered as the home feed collapsing to
the first page (and bouncing the viewport) whenever an upload was
polling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 19:27:25 -07:00
17a518429a Merge pull request 'Add Session Coach generation operations' (#303) from loewy/session-coach-agent-deploy into master
Reviewed-on: #303
2026-07-22 19:25:51 +00:00
33eb213cc9 Add Session Coach generation operations
All checks were successful
Tests / Tests (pull_request) Successful in 3m24s
2026-07-22 11:41:33 -07:00
3b66cb3270 Make video frame rate nullable 2026-07-22 02:20:15 -07:00
9 changed files with 854 additions and 8 deletions

View File

@@ -265,6 +265,7 @@ export type CohortStatsGql = {
activated7d: Scalars["Int"]["output"];
activatedNotPaid7d: Scalars["Int"]["output"];
paid7d: Scalars["Int"]["output"];
paidTotal: Scalars["Int"]["output"];
signups: Scalars["Int"]["output"];
weekStart: Scalars["Date"]["output"];
};
@@ -274,6 +275,7 @@ export type CommentGql = {
id: Scalars["Int"]["output"];
message: Scalars["String"]["output"];
replies: Array<CommentGql>;
replyToCommentId?: Maybe<Scalars["Int"]["output"]>;
user: UserGql;
};
@@ -314,6 +316,11 @@ export type CreatePoolHallInput = {
timezone?: InputMaybe<Scalars["String"]["input"]>;
};
export type CreateShotShareLinkInput = {
paddingSeconds?: InputMaybe<Scalars["Float"]["input"]>;
shotIds: Array<Scalars["Int"]["input"]>;
};
export type CreateSubscriptionResultGql = {
__typename?: "CreateSubscriptionResultGQL";
checkoutUrl: Scalars["String"]["output"];
@@ -2317,6 +2324,8 @@ export type FunnelStatsGql = {
freemiumEpoch: Scalars["Date"]["output"];
newPaid7d: Scalars["Int"]["output"];
newPaid30d: Scalars["Int"]["output"];
newPaidToday: Scalars["Int"]["output"];
pastDueUsers: Scalars["Int"]["output"];
payingUsers: Scalars["Int"]["output"];
retentionPrevActive: Scalars["Int"]["output"];
retentionReturned: Scalars["Int"]["output"];
@@ -2390,6 +2399,12 @@ export type GetUploadLinkReturn = {
value: UploadLinkGetUploadLinkErrors;
};
export type GetUploadLinksReturn = {
__typename?: "GetUploadLinksReturn";
stream?: Maybe<UploadStreamGql>;
value: UploadLinkBatchGetUploadLinkErrors;
};
export type HlsPlaylistGql = {
__typename?: "HLSPlaylistGQL";
m3u8Text: Scalars["String"]["output"];
@@ -2523,6 +2538,7 @@ export type Mutation = {
createPoolHall: PoolHall;
createPoolHallCamera: PoolHallCameraStreamCredentials;
createRuleSet: RuleSet;
createShotShareLink: ShareLinkGql;
createSubscription: CreateSubscriptionResultGql;
createUploadStream: CreateUploadStreamReturn;
deleteComment: Scalars["Boolean"]["output"];
@@ -2546,6 +2562,7 @@ export type Mutation = {
getHlsInitUploadLink: GetUploadLinkReturn;
getProfileImageUploadLink: GetProfileUploadLinkReturn;
getUploadLink: GetUploadLinkReturn;
getUploadLinks: GetUploadLinksReturn;
grantManualEntitlement: UserSubscriptionStatusGql;
inviteUsersToChallenge: Array<ChallengeInvitation>;
markAllNotificationsAsRead: Scalars["Boolean"]["output"];
@@ -2554,6 +2571,7 @@ export type Mutation = {
reactToVideo: Scalars["Boolean"]["output"];
recalculateChallengeEntry: ChallengeEntry;
reportContent: Scalars["Boolean"]["output"];
requestSessionCoachGeneration: SessionCoachGql;
requestVideoExport: VideoExportJobGql;
respondToChallengeInvitation: ChallengeInvitation;
retireTags: Scalars["Boolean"]["output"];
@@ -2635,6 +2653,10 @@ export type MutationCreateRuleSetArgs = {
name: Scalars["String"]["input"];
};
export type MutationCreateShotShareLinkArgs = {
input: CreateShotShareLinkInput;
};
export type MutationCreateSubscriptionArgs = {
priceId: Scalars["String"]["input"];
};
@@ -2729,6 +2751,11 @@ export type MutationGetUploadLinkArgs = {
videoId: Scalars["Int"]["input"];
};
export type MutationGetUploadLinksArgs = {
segmentIndexes: Array<Scalars["Int"]["input"]>;
videoId: Scalars["Int"]["input"];
};
export type MutationGrantManualEntitlementArgs = {
endsAt?: InputMaybe<Scalars["DateTime"]["input"]>;
reason?: InputMaybe<Scalars["String"]["input"]>;
@@ -2765,6 +2792,10 @@ export type MutationReportContentArgs = {
videoId: Scalars["Int"]["input"];
};
export type MutationRequestSessionCoachGenerationArgs = {
videoId: Scalars["Int"]["input"];
};
export type MutationRequestVideoExportArgs = {
input: RequestVideoExportInput;
};
@@ -3565,17 +3596,33 @@ export type SegmentStartTimesGql = {
segmentStartTimes: Array<Scalars["Float"]["output"]>;
};
export type SegmentUploadLink = {
__typename?: "SegmentUploadLink";
headers: Array<Maybe<Header>>;
segmentIndex: Scalars["Int"]["output"];
uploadUrl: Scalars["String"]["output"];
};
export type SerializedShotPathsGql = {
__typename?: "SerializedShotPathsGQL";
b64EncodedBuffer?: Maybe<Scalars["String"]["output"]>;
};
export type SessionCoachBaselineGql = {
__typename?: "SessionCoachBaselineGQL";
attemptCount: Scalars["Int"]["output"];
madeCount: Scalars["Int"]["output"];
makePercentage: Scalars["Float"]["output"];
sessionCount: Scalars["Int"]["output"];
};
export enum SessionCoachCandidateFamilyEnum {
Aiming = "AIMING",
BackCut = "BACK_CUT",
CueToObjectDistance = "CUE_TO_OBJECT_DISTANCE",
CutAngle = "CUT_ANGLE",
ObjectToPocketDistance = "OBJECT_TO_POCKET_DISTANCE",
Overall = "OVERALL",
Spin = "SPIN",
}
@@ -3595,14 +3642,39 @@ export type SessionCoachCandidateGql = {
};
export enum SessionCoachCandidateKindEnum {
BaselineComparison = "BASELINE_COMPARISON",
DirectionalMiss = "DIRECTIONAL_MISS",
OutcomePattern = "OUTCOME_PATTERN",
SessionStreak = "SESSION_STREAK",
StrengthPattern = "STRENGTH_PATTERN",
UsagePattern = "USAGE_PATTERN",
}
export type SessionCoachDecisionGql = {
__typename?: "SessionCoachDecisionGQL";
body?: Maybe<Scalars["String"]["output"]>;
drillId?: Maybe<Scalars["String"]["output"]>;
drillReason?: Maybe<Scalars["String"]["output"]>;
positiveBody?: Maybe<Scalars["String"]["output"]>;
positiveTitle?: Maybe<Scalars["String"]["output"]>;
practiceAttemptCount?: Maybe<Scalars["Int"]["output"]>;
practiceTarget?: Maybe<Scalars["String"]["output"]>;
selectedCandidateId?: Maybe<Scalars["ID"]["output"]>;
selectedPositiveCandidateId?: Maybe<Scalars["ID"]["output"]>;
sessionSummary?: Maybe<Scalars["String"]["output"]>;
suggestedAction?: Maybe<Scalars["String"]["output"]>;
supportingCandidateIds: Array<Scalars["ID"]["output"]>;
supportingShotIds: Array<Scalars["Int"]["output"]>;
title?: Maybe<Scalars["String"]["output"]>;
};
export type SessionCoachEvidenceGql = {
__typename?: "SessionCoachEvidenceGQL";
attemptCount: Scalars["Int"]["output"];
baselineAttemptCount: Scalars["Int"]["output"];
baselineMadeCount: Scalars["Int"]["output"];
baselineMakePercentage?: Maybe<Scalars["Float"]["output"]>;
baselineSessionCount: Scalars["Int"]["output"];
bucketKey: Scalars["String"]["output"];
bucketLabel: Scalars["String"]["output"];
comparisonAttemptCount: Scalars["Int"]["output"];
@@ -3618,17 +3690,21 @@ export type SessionCoachEvidenceGql = {
export type SessionCoachGql = {
__typename?: "SessionCoachGQL";
agentGeneration?: Maybe<SessionCoachGenerationGql>;
analysisVersion: Scalars["String"]["output"];
analyzedShotCount: Scalars["Int"]["output"];
candidates: Array<SessionCoachCandidateGql>;
generatedAt: Scalars["DateTime"]["output"];
ineligibilityReason?: Maybe<SessionCoachIneligibilityReasonEnum>;
longestMakeStreak: Scalars["Int"]["output"];
madeCount: Scalars["Int"]["output"];
makePercentage?: Maybe<Scalars["Float"]["output"]>;
missedCount: Scalars["Int"]["output"];
positiveCandidates: Array<SessionCoachCandidateGql>;
primaryCandidate?: Maybe<SessionCoachCandidateGql>;
processingId?: Maybe<Scalars["Int"]["output"]>;
processingStatus?: Maybe<Scalars["String"]["output"]>;
recentBaseline?: Maybe<SessionCoachBaselineGql>;
shotCount: Scalars["Int"]["output"];
state: SessionCoachStateEnum;
summary?: Maybe<Scalars["String"]["output"]>;
@@ -3636,6 +3712,45 @@ export type SessionCoachGql = {
videoId: Scalars["Int"]["output"];
};
export type SessionCoachGenerationDiagnosticsGql = {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
abstentionReason?: Maybe<Scalars["String"]["output"]>;
analysisVersion: Scalars["String"]["output"];
attemptCount: Scalars["Int"]["output"];
cachedInputTokens?: Maybe<Scalars["Int"]["output"]>;
enqueueCount: Scalars["Int"]["output"];
failureCode?: Maybe<Scalars["String"]["output"]>;
inputHash: Scalars["String"]["output"];
inputTokens?: Maybe<Scalars["Int"]["output"]>;
lastEnqueuedAt?: Maybe<Scalars["DateTime"]["output"]>;
latencyMs?: Maybe<Scalars["Int"]["output"]>;
maxAttempts: Scalars["Int"]["output"];
modelName: Scalars["String"]["output"];
outputTokens?: Maybe<Scalars["Int"]["output"]>;
promptVersion: Scalars["String"]["output"];
providerKey: Scalars["String"]["output"];
startedAt?: Maybe<Scalars["DateTime"]["output"]>;
synthesisRationale?: Maybe<Scalars["String"]["output"]>;
updatedAt: Scalars["DateTime"]["output"];
};
export type SessionCoachGenerationGql = {
__typename?: "SessionCoachGenerationGQL";
completedAt?: Maybe<Scalars["DateTime"]["output"]>;
createdAt: Scalars["DateTime"]["output"];
decision?: Maybe<SessionCoachDecisionGql>;
diagnostics?: Maybe<SessionCoachGenerationDiagnosticsGql>;
id: Scalars["ID"]["output"];
state: SessionCoachGenerationStateEnum;
};
export enum SessionCoachGenerationStateEnum {
Failed = "FAILED",
Queued = "QUEUED",
Ready = "READY",
Running = "RUNNING",
}
export enum SessionCoachIneligibilityReasonEnum {
Multiplayer = "MULTIPLAYER",
}
@@ -3648,6 +3763,12 @@ export enum SessionCoachStateEnum {
Ready = "READY",
}
export type ShareLinkGql = {
__typename?: "ShareLinkGQL";
slug: Scalars["String"]["output"];
url: Scalars["String"]["output"];
};
export type ShotAnnotationGql = {
__typename?: "ShotAnnotationGQL";
createdAt?: Maybe<Scalars["DateTime"]["output"]>;
@@ -4054,6 +4175,16 @@ export type UploadLink = {
uploadUrl: Scalars["String"]["output"];
};
export type UploadLinkBatch = {
__typename?: "UploadLinkBatch";
alreadyUploaded: Array<Scalars["Int"]["output"]>;
links: Array<SegmentUploadLink>;
};
export type UploadLinkBatchGetUploadLinkErrors =
| GetUploadLinkErrors
| UploadLinkBatch;
export type UploadLinkGetProfileUploadLinkErrors =
| GetProfileUploadLinkErrors
| UploadLink;
@@ -4259,7 +4390,7 @@ export type VideoGql = {
currentProcessing?: Maybe<VideoProcessingGql>;
elapsedTime?: Maybe<Scalars["Float"]["output"]>;
endTime?: Maybe<Scalars["DateTime"]["output"]>;
framesPerSecond: Scalars["Float"]["output"];
framesPerSecond?: Maybe<Scalars["Float"]["output"]>;
homographyHistory: Array<HomographyInfoGql>;
id: Scalars["Int"]["output"];
makePercentage: Scalars["Float"]["output"];
@@ -4952,6 +5083,7 @@ export type GetFeedQuery = {
__typename?: "CommentGQL";
id: number;
message: string;
replyToCommentId?: number | null;
user: {
__typename?: "UserGQL";
id: number;
@@ -5067,6 +5199,7 @@ export type VideoCardFieldsFragment = {
__typename?: "CommentGQL";
id: number;
message: string;
replyToCommentId?: number | null;
user: {
__typename?: "UserGQL";
id: number;
@@ -5215,6 +5348,7 @@ export type GetVideoFeedQuery = {
__typename?: "CommentGQL";
id: number;
message: string;
replyToCommentId?: number | null;
user: {
__typename?: "UserGQL";
id: number;
@@ -5354,6 +5488,69 @@ export type GetMyDrillRunsQuery = {
}>;
};
export type GetRecordedStreamsQueryVariables = Exact<{
limit?: Scalars["Int"]["input"];
}>;
export type GetRecordedStreamsQuery = {
__typename?: "Query";
getFeedVideos: {
__typename?: "VideoHistoryGQL";
videos: Array<{
__typename?: "VideoGQL";
id: number;
name?: string | null;
createdAt?: any | null;
screenshotUri?: string | null;
framesPerSecond?: number | null;
totalShots: number;
owner?: {
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
} | null;
}>;
};
};
export type GetLiveStreamsQueryVariables = Exact<{
limit?: Scalars["Int"]["input"];
}>;
export type GetLiveStreamsQuery = {
__typename?: "Query";
getFeedVideos: {
__typename?: "VideoHistoryGQL";
videos: Array<{
__typename?: "VideoGQL";
id: number;
name?: string | null;
startTime?: any | null;
createdAt?: any | null;
screenshotUri?: string | null;
elapsedTime?: number | null;
owner?: {
__typename?: "UserGQL";
id: number;
username: string;
profileImageUri?: string | null;
} | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean;
lastSegmentUploadedAt?: any | null;
} | null;
}>;
pageInfo: {
__typename?: "PageInfoGQL";
hasNextPage: boolean;
endCursor?: string | null;
};
};
};
export type GetVideoMakePercentageIntervalsQueryVariables = Exact<{
videoId: Scalars["ID"]["input"];
intervalDuration: Scalars["Int"]["input"];
@@ -6710,7 +6907,7 @@ export type GetShotLabVideosQuery = {
name?: string | null;
createdAt?: any | null;
screenshotUri?: string | null;
framesPerSecond: number;
framesPerSecond?: number | null;
totalShots: number;
owner?: {
__typename?: "UserGQL";
@@ -6911,6 +7108,94 @@ export type GetSessionCoachQuery = {
rankReason: string;
};
}>;
agentGeneration?: {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
title?: string | null;
body?: string | null;
suggestedAction?: string | null;
drillId?: string | null;
drillReason?: string | null;
practiceAttemptCount?: number | null;
practiceTarget?: string | null;
} | null;
diagnostics?: {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
analysisVersion: string;
promptVersion: string;
providerKey: string;
modelName: string;
inputHash: string;
attemptCount: number;
maxAttempts: number;
enqueueCount: number;
latencyMs?: number | null;
inputTokens?: number | null;
cachedInputTokens?: number | null;
outputTokens?: number | null;
failureCode?: string | null;
updatedAt: any;
startedAt?: any | null;
lastEnqueuedAt?: any | null;
abstentionReason?: string | null;
} | null;
} | null;
};
};
export type RequestSessionCoachGenerationMutationVariables = Exact<{
videoId: Scalars["Int"]["input"];
}>;
export type RequestSessionCoachGenerationMutation = {
__typename?: "Mutation";
requestSessionCoachGeneration: {
__typename?: "SessionCoachGQL";
state: SessionCoachStateEnum;
agentGeneration?: {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
title?: string | null;
body?: string | null;
suggestedAction?: string | null;
drillId?: string | null;
drillReason?: string | null;
practiceAttemptCount?: number | null;
practiceTarget?: string | null;
} | null;
diagnostics?: {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
analysisVersion: string;
promptVersion: string;
providerKey: string;
modelName: string;
inputHash: string;
attemptCount: number;
maxAttempts: number;
enqueueCount: number;
latencyMs?: number | null;
inputTokens?: number | null;
cachedInputTokens?: number | null;
outputTokens?: number | null;
failureCode?: string | null;
updatedAt: any;
startedAt?: any | null;
lastEnqueuedAt?: any | null;
abstentionReason?: string | null;
} | null;
} | null;
};
};
@@ -6943,6 +7228,45 @@ export type SessionCoachCandidateFieldsFragment = {
};
};
export type SessionCoachGenerationFieldsFragment = {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
title?: string | null;
body?: string | null;
suggestedAction?: string | null;
drillId?: string | null;
drillReason?: string | null;
practiceAttemptCount?: number | null;
practiceTarget?: string | null;
} | null;
diagnostics?: {
__typename?: "SessionCoachGenerationDiagnosticsGQL";
analysisVersion: string;
promptVersion: string;
providerKey: string;
modelName: string;
inputHash: string;
attemptCount: number;
maxAttempts: number;
enqueueCount: number;
latencyMs?: number | null;
inputTokens?: number | null;
cachedInputTokens?: number | null;
outputTokens?: number | null;
failureCode?: string | null;
updatedAt: any;
startedAt?: any | null;
lastEnqueuedAt?: any | null;
abstentionReason?: string | null;
} | null;
};
export type PlayerSummaryFieldsFragment = {
__typename?: "PlayerSummaryGQL";
clusterId: number;
@@ -7740,6 +8064,8 @@ export type GetUsageStatsQuery = {
activationCohort: number;
activationActivated: number;
payingUsers: number;
pastDueUsers: number;
newPaidToday: number;
newPaid7d: number;
newPaid30d: number;
retentionPrevActive: number;
@@ -7752,6 +8078,7 @@ export type GetUsageStatsQuery = {
activated7d: number;
paid7d: number;
activatedNotPaid7d: number;
paidTotal: number;
}>;
windows: Array<{
__typename?: "UsageStatsWindowGQL";
@@ -8331,6 +8658,7 @@ export type GetVideoSocialDetailsByIdQuery = {
__typename?: "CommentGQL";
id: number;
message: string;
replyToCommentId?: number | null;
user: {
__typename?: "UserGQL";
id: number;
@@ -8438,6 +8766,7 @@ export type GetVideoCardQuery = {
__typename?: "CommentGQL";
id: number;
message: string;
replyToCommentId?: number | null;
user: {
__typename?: "UserGQL";
id: number;
@@ -8459,7 +8788,7 @@ export type GetVideosQuery = {
getVideos: Array<{
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
@@ -8483,7 +8812,7 @@ export type GetVideosQuery = {
export type VideoStreamMetadataFragment = {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
@@ -8512,7 +8841,7 @@ export type GetVideoForShotTimeQuery = {
getVideo: {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
@@ -8683,7 +9012,7 @@ export type PlaylistWithSegmentStartTimesFragment = {
export type VideoDurationDataFragment = {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
playlist?: {
__typename?: "HLSPlaylistGQL";
videoId: number;
@@ -8720,7 +9049,7 @@ export type GetVideoForClipTimesQuery = {
getVideo: {
__typename?: "VideoGQL";
id: number;
framesPerSecond: number;
framesPerSecond?: number | null;
playlist?: {
__typename?: "HLSPlaylistGQL";
videoId: number;
@@ -9015,6 +9344,7 @@ export type GetUploadLinkMutation = {
};
stream?: {
__typename?: "UploadStreamGQL";
id: string;
uploadCompletionCursor: number;
} | null;
};
@@ -9113,6 +9443,7 @@ export type UploadStreamWithDetailsFragment = {
startTime?: any | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean;
lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number;
@@ -9137,6 +9468,7 @@ export type GetUploadStreamsWithDetailsQuery = {
startTime?: any | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean;
lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number;
@@ -9238,6 +9570,7 @@ export const VideoCardFieldsFragmentDoc = gql`
replies {
id
message
replyToCommentId
user {
...UserSocialsFields
}
@@ -9417,6 +9750,43 @@ export const SessionCoachCandidateFieldsFragmentDoc = gql`
}
}
`;
export const SessionCoachGenerationFieldsFragmentDoc = gql`
fragment SessionCoachGenerationFields on SessionCoachGenerationGQL {
id
state
createdAt
completedAt
decision {
selectedCandidateId
title
body
suggestedAction
drillId
drillReason
practiceAttemptCount
practiceTarget
}
diagnostics {
analysisVersion
promptVersion
providerKey
modelName
inputHash
attemptCount
maxAttempts
enqueueCount
latencyMs
inputTokens
cachedInputTokens
outputTokens
failureCode
updatedAt
startedAt
lastEnqueuedAt
abstentionReason
}
}
`;
export const PlayerClusterShotFieldsFragmentDoc = gql`
fragment PlayerClusterShotFields on PlayerClusterShotGQL {
shotId
@@ -9676,6 +10046,7 @@ export const UploadStreamWithDetailsFragmentDoc = gql`
name
startTime
stream {
id
isCompleted
lastIntendedSegmentBound
uploadCompletionCursor
@@ -12154,6 +12525,195 @@ export type GetMyDrillRunsQueryResult = Apollo.QueryResult<
GetMyDrillRunsQuery,
GetMyDrillRunsQueryVariables
>;
export const GetRecordedStreamsDocument = gql`
query GetRecordedStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
createdAt
screenshotUri
framesPerSecond
totalShots
owner {
id
username
profileImageUri
}
}
}
}
`;
/**
* __useGetRecordedStreamsQuery__
*
* To run a query within a React component, call `useGetRecordedStreamsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetRecordedStreamsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetRecordedStreamsQuery({
* variables: {
* limit: // value for 'limit'
* },
* });
*/
export function useGetRecordedStreamsQuery(
baseOptions?: Apollo.QueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
}
export function useGetRecordedStreamsLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
}
export function useGetRecordedStreamsSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
}
export type GetRecordedStreamsQueryHookResult = ReturnType<
typeof useGetRecordedStreamsQuery
>;
export type GetRecordedStreamsLazyQueryHookResult = ReturnType<
typeof useGetRecordedStreamsLazyQuery
>;
export type GetRecordedStreamsSuspenseQueryHookResult = ReturnType<
typeof useGetRecordedStreamsSuspenseQuery
>;
export type GetRecordedStreamsQueryResult = Apollo.QueryResult<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>;
export const GetLiveStreamsDocument = gql`
query GetLiveStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: false, requireCursorCompletion: false }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
startTime
createdAt
screenshotUri
elapsedTime
owner {
id
username
profileImageUri
}
stream {
id
isCompleted
lastSegmentUploadedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
/**
* __useGetLiveStreamsQuery__
*
* To run a query within a React component, call `useGetLiveStreamsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetLiveStreamsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetLiveStreamsQuery({
* variables: {
* limit: // value for 'limit'
* },
* });
*/
export function useGetLiveStreamsQuery(
baseOptions?: Apollo.QueryHookOptions<
GetLiveStreamsQuery,
GetLiveStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<GetLiveStreamsQuery, GetLiveStreamsQueryVariables>(
GetLiveStreamsDocument,
options,
);
}
export function useGetLiveStreamsLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetLiveStreamsQuery,
GetLiveStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<GetLiveStreamsQuery, GetLiveStreamsQueryVariables>(
GetLiveStreamsDocument,
options,
);
}
export function useGetLiveStreamsSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetLiveStreamsQuery,
GetLiveStreamsQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetLiveStreamsQuery,
GetLiveStreamsQueryVariables
>(GetLiveStreamsDocument, options);
}
export type GetLiveStreamsQueryHookResult = ReturnType<
typeof useGetLiveStreamsQuery
>;
export type GetLiveStreamsLazyQueryHookResult = ReturnType<
typeof useGetLiveStreamsLazyQuery
>;
export type GetLiveStreamsSuspenseQueryHookResult = ReturnType<
typeof useGetLiveStreamsSuspenseQuery
>;
export type GetLiveStreamsQueryResult = Apollo.QueryResult<
GetLiveStreamsQuery,
GetLiveStreamsQueryVariables
>;
export const GetVideoMakePercentageIntervalsDocument = gql`
query GetVideoMakePercentageIntervals(
$videoId: ID!
@@ -14862,9 +15422,13 @@ export const GetSessionCoachDocument = gql`
candidates {
...SessionCoachCandidateFields
}
agentGeneration {
...SessionCoachGenerationFields
}
}
}
${SessionCoachCandidateFieldsFragmentDoc}
${SessionCoachGenerationFieldsFragmentDoc}
`;
/**
@@ -14932,6 +15496,61 @@ export type GetSessionCoachQueryResult = Apollo.QueryResult<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>;
export const RequestSessionCoachGenerationDocument = gql`
mutation RequestSessionCoachGeneration($videoId: Int!) {
requestSessionCoachGeneration(videoId: $videoId) {
state
agentGeneration {
...SessionCoachGenerationFields
}
}
}
${SessionCoachGenerationFieldsFragmentDoc}
`;
export type RequestSessionCoachGenerationMutationFn = Apollo.MutationFunction<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>;
/**
* __useRequestSessionCoachGenerationMutation__
*
* To run a mutation, you first call `useRequestSessionCoachGenerationMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRequestSessionCoachGenerationMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [requestSessionCoachGenerationMutation, { data, loading, error }] = useRequestSessionCoachGenerationMutation({
* variables: {
* videoId: // value for 'videoId'
* },
* });
*/
export function useRequestSessionCoachGenerationMutation(
baseOptions?: Apollo.MutationHookOptions<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useMutation<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>(RequestSessionCoachGenerationDocument, options);
}
export type RequestSessionCoachGenerationMutationHookResult = ReturnType<
typeof useRequestSessionCoachGenerationMutation
>;
export type RequestSessionCoachGenerationMutationResult =
Apollo.MutationResult<RequestSessionCoachGenerationMutation>;
export type RequestSessionCoachGenerationMutationOptions =
Apollo.BaseMutationOptions<
RequestSessionCoachGenerationMutation,
RequestSessionCoachGenerationMutationVariables
>;
export const VideoPlayerClustersDocument = gql`
query VideoPlayerClusters($videoId: Int!) {
videoPlayerClusters(videoId: $videoId) {
@@ -16392,6 +17011,8 @@ export const GetUsageStatsDocument = gql`
activationCohort
activationActivated
payingUsers
pastDueUsers
newPaidToday
newPaid7d
newPaid30d
retentionPrevActive
@@ -16403,6 +17024,7 @@ export const GetUsageStatsDocument = gql`
activated7d
paid7d
activatedNotPaid7d
paidTotal
}
totalUsers
totalVideos
@@ -18086,6 +18708,7 @@ export const GetVideoSocialDetailsByIdDocument = gql`
replies {
id
message
replyToCommentId
user {
...UserSocialsFields
}
@@ -19393,6 +20016,7 @@ export const GetUploadLinkDocument = gql`
}
}
stream {
id
uploadCompletionCursor
}
}

View File

@@ -78,6 +78,7 @@ fragment VideoCardFields on VideoGQL {
replies {
id
message
replyToCommentId
user {
...UserSocialsFields
}

View File

@@ -0,0 +1,25 @@
# Legacy shot-sim operations still used by mobile master's
# LiveStreamPicker / RecordedShotPicker; remove when those are deleted.
query GetRecordedStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
createdAt
screenshotUri
framesPerSecond
totalShots
owner {
id
username
profileImageUri
}
}
}
}

View File

@@ -0,0 +1,34 @@
# Lightweight listing of live (in-progress) streams for pickers like the
# Shot Lab "follow a live stream" flow. Selects only what a picker card
# needs; intentionally avoids the heavy VideoCardFields fragment.
query GetLiveStreams($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: false, requireCursorCompletion: false }
includePrivate: MINE
feedInput: { allUsers: true }
) {
videos {
id
name
startTime
createdAt
screenshotUri
elapsedTime
owner {
id
username
profileImageUri
}
stream {
id
isCompleted
lastSegmentUploadedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}

View File

@@ -20,6 +20,18 @@ query GetSessionCoach($videoId: Int!) {
candidates {
...SessionCoachCandidateFields
}
agentGeneration {
...SessionCoachGenerationFields
}
}
}
mutation RequestSessionCoachGeneration($videoId: Int!) {
requestSessionCoachGeneration(videoId: $videoId) {
state
agentGeneration {
...SessionCoachGenerationFields
}
}
}
@@ -49,3 +61,39 @@ fragment SessionCoachCandidateFields on SessionCoachCandidateGQL {
rankReason
}
}
fragment SessionCoachGenerationFields on SessionCoachGenerationGQL {
id
state
createdAt
completedAt
decision {
selectedCandidateId
title
body
suggestedAction
drillId
drillReason
practiceAttemptCount
practiceTarget
}
diagnostics {
analysisVersion
promptVersion
providerKey
modelName
inputHash
attemptCount
maxAttempts
enqueueCount
latencyMs
inputTokens
cachedInputTokens
outputTokens
failureCode
updatedAt
startedAt
lastEnqueuedAt
abstentionReason
}
}

View File

@@ -7,6 +7,8 @@ query getUsageStats($days: Int! = 30) {
activationCohort
activationActivated
payingUsers
pastDueUsers
newPaidToday
newPaid7d
newPaid30d
retentionPrevActive
@@ -18,6 +20,7 @@ query getUsageStats($days: Int! = 30) {
activated7d
paid7d
activatedNotPaid7d
paidTotal
}
totalUsers
totalVideos

View File

@@ -131,6 +131,7 @@ query GetVideoSocialDetailsById($videoId: Int!) {
replies {
id
message
replyToCommentId
user {
...UserSocialsFields
}

View File

@@ -48,6 +48,7 @@ mutation GetUploadLink($videoId: Int!, $segmentIndex: Int!) {
}
}
stream {
id
uploadCompletionCursor
}
}
@@ -127,6 +128,7 @@ fragment UploadStreamWithDetails on VideoGQL {
name
startTime
stream {
id
isCompleted
lastIntendedSegmentBound
uploadCompletionCursor

View File

@@ -214,6 +214,7 @@ type CohortStatsGQL {
activated7d: Int!
paid7d: Int!
activatedNotPaid7d: Int!
paidTotal: Int!
}
type CommentGQL {
@@ -221,6 +222,7 @@ type CommentGQL {
user: UserGQL!
message: String!
replies: [CommentGQL!]!
replyToCommentId: Int
}
type CountLeaderboardGQL {
@@ -258,6 +260,11 @@ input CreatePoolHallInput {
timezone: String = null
}
input CreateShotShareLinkInput {
shotIds: [Int!]!
paddingSeconds: Float = null
}
type CreateSubscriptionResultGQL {
checkoutUrl: String!
sessionId: String!
@@ -458,6 +465,8 @@ type FunnelStatsGQL {
activationCohort: Int!
activationActivated: Int!
payingUsers: Int!
pastDueUsers: Int!
newPaidToday: Int!
newPaid7d: Int!
newPaid30d: Int!
retentionPrevActive: Int!
@@ -525,6 +534,11 @@ type GetUploadLinkReturn {
stream: UploadStreamGQL
}
type GetUploadLinksReturn {
value: UploadLinkBatchGetUploadLinkErrors!
stream: UploadStreamGQL
}
type HLSPlaylistGQL {
videoId: Int!
m3u8Text: String!
@@ -700,6 +714,7 @@ type Mutation {
cancelCameraClaimSession(claimSessionId: ID!): CameraClaimSession!
endCameraLease(leaseId: ID!): CameraLease!
extendCameraLease(leaseId: ID!, additionalMinutes: Int! = 60): CameraLease!
createShotShareLink(input: CreateShotShareLinkInput!): ShareLinkGQL!
finalizePlayerAssignments(
input: FinalizePlayerAssignmentsInput!
): [PlayerClusterGQL!]!
@@ -716,6 +731,7 @@ type Mutation {
shotId: Int!
fieldsToEdit: EditableShotFieldInputGQL!
): EditShotReturn!
requestSessionCoachGeneration(videoId: Int!): SessionCoachGQL!
getProfileImageUploadLink(
fileExt: String = ".png"
): GetProfileUploadLinkReturn!
@@ -753,6 +769,7 @@ type Mutation {
expectedDurationSeconds: Float = null
): CreateUploadStreamReturn!
getUploadLink(videoId: Int!, segmentIndex: Int!): GetUploadLinkReturn!
getUploadLinks(videoId: Int!, segmentIndexes: [Int!]!): GetUploadLinksReturn!
getHlsInitUploadLink(videoId: Int!): GetUploadLinkReturn!
setSegmentDuration(
videoId: Int!
@@ -1287,11 +1304,25 @@ type SegmentAlreadyUploadedErr {
segmentId: Int!
}
type SegmentUploadLink {
segmentIndex: Int!
uploadUrl: String!
headers: [Header]!
}
type SerializedShotPathsGQL {
b64EncodedBuffer: String
}
type SessionCoachBaselineGQL {
sessionCount: Int!
attemptCount: Int!
madeCount: Int!
makePercentage: Float!
}
enum SessionCoachCandidateFamilyEnum {
OVERALL
AIMING
CUT_ANGLE
SPIN
@@ -1315,11 +1346,31 @@ type SessionCoachCandidateGQL {
}
enum SessionCoachCandidateKindEnum {
BASELINE_COMPARISON
SESSION_STREAK
STRENGTH_PATTERN
DIRECTIONAL_MISS
OUTCOME_PATTERN
USAGE_PATTERN
}
type SessionCoachDecisionGQL {
selectedPositiveCandidateId: ID
positiveTitle: String
positiveBody: String
selectedCandidateId: ID
supportingCandidateIds: [ID!]!
supportingShotIds: [Int!]!
sessionSummary: String
title: String
body: String
suggestedAction: String
drillId: String
drillReason: String
practiceAttemptCount: Int
practiceTarget: String
}
type SessionCoachEvidenceGQL {
bucketKey: String!
bucketLabel: String!
@@ -1333,6 +1384,10 @@ type SessionCoachEvidenceGQL {
patternShotCount: Int!
score: Float!
rankReason: String!
baselineSessionCount: Int!
baselineAttemptCount: Int!
baselineMadeCount: Int!
baselineMakePercentage: Float
}
type SessionCoachGQL {
@@ -1350,8 +1405,49 @@ type SessionCoachGQL {
unknownOutcomeCount: Int!
analyzedShotCount: Int!
makePercentage: Float
longestMakeStreak: Int!
recentBaseline: SessionCoachBaselineGQL
positiveCandidates: [SessionCoachCandidateGQL!]!
primaryCandidate: SessionCoachCandidateGQL
candidates: [SessionCoachCandidateGQL!]!
agentGeneration: SessionCoachGenerationGQL
}
type SessionCoachGenerationDiagnosticsGQL {
analysisVersion: String!
promptVersion: String!
providerKey: String!
modelName: String!
inputHash: String!
attemptCount: Int!
maxAttempts: Int!
enqueueCount: Int!
latencyMs: Int
inputTokens: Int
cachedInputTokens: Int
outputTokens: Int
failureCode: String
updatedAt: DateTime!
startedAt: DateTime
lastEnqueuedAt: DateTime
abstentionReason: String
synthesisRationale: String
}
type SessionCoachGenerationGQL {
id: ID!
state: SessionCoachGenerationStateEnum!
createdAt: DateTime!
completedAt: DateTime
decision: SessionCoachDecisionGQL
diagnostics: SessionCoachGenerationDiagnosticsGQL
}
enum SessionCoachGenerationStateEnum {
QUEUED
RUNNING
READY
FAILED
}
enum SessionCoachIneligibilityReasonEnum {
@@ -1366,6 +1462,11 @@ enum SessionCoachStateEnum {
FAILED
}
type ShareLinkGQL {
slug: String!
url: String!
}
type ShotAnnotationGQL {
shotId: Int!
type: ShotAnnotationTypeGQL!
@@ -1700,6 +1801,13 @@ type UploadLink {
headers: [Header]!
}
type UploadLinkBatch {
links: [SegmentUploadLink!]!
alreadyUploaded: [Int!]!
}
union UploadLinkBatchGetUploadLinkErrors = UploadLinkBatch | GetUploadLinkErrors
union UploadLinkGetProfileUploadLinkErrors =
UploadLink
| GetProfileUploadLinkErrors
@@ -1880,7 +1988,7 @@ type VideoGQL {
startTime: DateTime
endTime: DateTime
elapsedTime: Float
framesPerSecond: Float!
framesPerSecond: Float
tableSize: Float!
pocketSize: Float
private: Boolean!