Compare commits

..

1 Commits

Author SHA1 Message Date
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
7 changed files with 1819 additions and 2146 deletions

View File

@@ -2404,11 +2404,6 @@ export type Header = {
value: Scalars["String"]["output"]; value: Scalars["String"]["output"];
}; };
export enum HomographyBackendGql {
Classic = "CLASSIC",
Tablenet = "TABLENET",
}
export type HomographyInfoGql = { export type HomographyInfoGql = {
__typename?: "HomographyInfoGQL"; __typename?: "HomographyInfoGQL";
crop: BoundingBoxGql; crop: BoundingBoxGql;
@@ -2554,7 +2549,6 @@ export type Mutation = {
reactToVideo: Scalars["Boolean"]["output"]; reactToVideo: Scalars["Boolean"]["output"];
recalculateChallengeEntry: ChallengeEntry; recalculateChallengeEntry: ChallengeEntry;
reportContent: Scalars["Boolean"]["output"]; reportContent: Scalars["Boolean"]["output"];
requestSessionCoachGeneration: SessionCoachGql;
requestVideoExport: VideoExportJobGql; requestVideoExport: VideoExportJobGql;
respondToChallengeInvitation: ChallengeInvitation; respondToChallengeInvitation: ChallengeInvitation;
retireTags: Scalars["Boolean"]["output"]; retireTags: Scalars["Boolean"]["output"];
@@ -2766,10 +2760,6 @@ export type MutationReportContentArgs = {
videoId: Scalars["Int"]["input"]; videoId: Scalars["Int"]["input"];
}; };
export type MutationRequestSessionCoachGenerationArgs = {
videoId: Scalars["Int"]["input"];
};
export type MutationRequestVideoExportArgs = { export type MutationRequestVideoExportArgs = {
input: RequestVideoExportInput; input: RequestVideoExportInput;
}; };
@@ -3200,28 +3190,14 @@ export type QueryGetLiveTableStateArgs = {
}; };
export type QueryGetLongestRunsLeaderboardArgs = { export type QueryGetLongestRunsLeaderboardArgs = {
excludeDrills?: Scalars["Boolean"]["input"];
gameType?: InputMaybe<Scalars["String"]["input"]>;
interval?: InputMaybe<TimeInterval>; interval?: InputMaybe<TimeInterval>;
limit?: Scalars["Int"]["input"]; limit?: Scalars["Int"]["input"];
pocketSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
rankedOnly?: Scalars["Boolean"]["input"];
requiredTags?: InputMaybe<Array<Scalars["String"]["input"]>>; requiredTags?: InputMaybe<Array<Scalars["String"]["input"]>>;
tableSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
tableSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
when?: InputMaybe<Scalars["DateTime"]["input"]>; when?: InputMaybe<Scalars["DateTime"]["input"]>;
}; };
export type QueryGetMakesLeaderboardArgs = { export type QueryGetMakesLeaderboardArgs = {
excludeDrills?: Scalars["Boolean"]["input"];
gameType?: InputMaybe<Scalars["String"]["input"]>;
interval?: InputMaybe<TimeInterval>; interval?: InputMaybe<TimeInterval>;
pocketSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
rankedOnly?: Scalars["Boolean"]["input"];
tableSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
tableSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
when?: InputMaybe<Scalars["DateTime"]["input"]>; when?: InputMaybe<Scalars["DateTime"]["input"]>;
}; };
@@ -3605,21 +3581,6 @@ export enum SessionCoachCandidateKindEnum {
UsagePattern = "USAGE_PATTERN", UsagePattern = "USAGE_PATTERN",
} }
export type SessionCoachDecisionGql = {
__typename?: "SessionCoachDecisionGQL";
body?: Maybe<Scalars["String"]["output"]>;
drillId?: Maybe<Scalars["String"]["output"]>;
drillReason?: Maybe<Scalars["String"]["output"]>;
practiceAttemptCount?: Maybe<Scalars["Int"]["output"]>;
practiceTarget?: Maybe<Scalars["String"]["output"]>;
selectedCandidateId?: 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 = { export type SessionCoachEvidenceGql = {
__typename?: "SessionCoachEvidenceGQL"; __typename?: "SessionCoachEvidenceGQL";
attemptCount: Scalars["Int"]["output"]; attemptCount: Scalars["Int"]["output"];
@@ -3638,7 +3599,6 @@ export type SessionCoachEvidenceGql = {
export type SessionCoachGql = { export type SessionCoachGql = {
__typename?: "SessionCoachGQL"; __typename?: "SessionCoachGQL";
agentGeneration?: Maybe<SessionCoachGenerationGql>;
analysisVersion: Scalars["String"]["output"]; analysisVersion: Scalars["String"]["output"];
analyzedShotCount: Scalars["Int"]["output"]; analyzedShotCount: Scalars["Int"]["output"];
candidates: Array<SessionCoachCandidateGql>; candidates: Array<SessionCoachCandidateGql>;
@@ -3657,45 +3617,6 @@ export type SessionCoachGql = {
videoId: Scalars["Int"]["output"]; 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 { export enum SessionCoachIneligibilityReasonEnum {
Multiplayer = "MULTIPLAYER", Multiplayer = "MULTIPLAYER",
} }
@@ -4319,7 +4240,7 @@ export type VideoGql = {
currentProcessing?: Maybe<VideoProcessingGql>; currentProcessing?: Maybe<VideoProcessingGql>;
elapsedTime?: Maybe<Scalars["Float"]["output"]>; elapsedTime?: Maybe<Scalars["Float"]["output"]>;
endTime?: Maybe<Scalars["DateTime"]["output"]>; endTime?: Maybe<Scalars["DateTime"]["output"]>;
framesPerSecond?: Maybe<Scalars["Float"]["output"]>; framesPerSecond: Scalars["Float"]["output"];
homographyHistory: Array<HomographyInfoGql>; homographyHistory: Array<HomographyInfoGql>;
id: Scalars["Int"]["output"]; id: Scalars["Int"]["output"];
makePercentage: Scalars["Float"]["output"]; makePercentage: Scalars["Float"]["output"];
@@ -4357,7 +4278,6 @@ export type VideoMetadataInput = {
framesPerSecond?: InputMaybe<Scalars["Float"]["input"]>; framesPerSecond?: InputMaybe<Scalars["Float"]["input"]>;
/** @deprecated `game_type` is deprecated. Use `tags` instead. */ /** @deprecated `game_type` is deprecated. Use `tags` instead. */
gameType?: InputMaybe<Scalars["String"]["input"]>; gameType?: InputMaybe<Scalars["String"]["input"]>;
homographyBackend?: InputMaybe<HomographyBackendGql>;
lastIntendedSegmentBound?: InputMaybe<Scalars["Int"]["input"]>; lastIntendedSegmentBound?: InputMaybe<Scalars["Int"]["input"]>;
pocketSize?: InputMaybe<Scalars["Float"]["input"]>; pocketSize?: InputMaybe<Scalars["Float"]["input"]>;
private?: InputMaybe<Scalars["Boolean"]["input"]>; private?: InputMaybe<Scalars["Boolean"]["input"]>;
@@ -5296,11 +5216,6 @@ export type GetVideoFeedQuery = {
export type GetMakesLeaderboardQueryVariables = Exact<{ export type GetMakesLeaderboardQueryVariables = Exact<{
interval?: InputMaybe<TimeInterval>; interval?: InputMaybe<TimeInterval>;
when?: InputMaybe<Scalars["DateTime"]["input"]>; when?: InputMaybe<Scalars["DateTime"]["input"]>;
gameType?: InputMaybe<Scalars["String"]["input"]>;
tableSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
tableSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
}>; }>;
export type GetMakesLeaderboardQuery = { export type GetMakesLeaderboardQuery = {
@@ -5325,11 +5240,6 @@ export type GetMakesLeaderboardQuery = {
export type GetRunsLeaderboardQueryVariables = Exact<{ export type GetRunsLeaderboardQueryVariables = Exact<{
interval?: InputMaybe<TimeInterval>; interval?: InputMaybe<TimeInterval>;
when?: InputMaybe<Scalars["DateTime"]["input"]>; when?: InputMaybe<Scalars["DateTime"]["input"]>;
gameType?: InputMaybe<Scalars["String"]["input"]>;
tableSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
tableSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
}>; }>;
export type GetRunsLeaderboardQuery = { export type GetRunsLeaderboardQuery = {
@@ -5414,6 +5324,43 @@ export type GetMyDrillRunsQuery = {
}>; }>;
}; };
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<{ export type GetVideoMakePercentageIntervalsQueryVariables = Exact<{
videoId: Scalars["ID"]["input"]; videoId: Scalars["ID"]["input"];
intervalDuration: Scalars["Int"]["input"]; intervalDuration: Scalars["Int"]["input"];
@@ -6755,12 +6702,11 @@ export type ReactToVideoMutation = {
reactToVideo: boolean; reactToVideo: boolean;
}; };
export type GetShotLabVideosQueryVariables = Exact<{ export type GetRecordedStreamsQueryVariables = Exact<{
userId: Scalars["Int"]["input"];
limit?: Scalars["Int"]["input"]; limit?: Scalars["Int"]["input"];
}>; }>;
export type GetShotLabVideosQuery = { export type GetRecordedStreamsQuery = {
__typename?: "Query"; __typename?: "Query";
getFeedVideos: { getFeedVideos: {
__typename?: "VideoHistoryGQL"; __typename?: "VideoHistoryGQL";
@@ -6770,7 +6716,7 @@ export type GetShotLabVideosQuery = {
name?: string | null; name?: string | null;
createdAt?: any | null; createdAt?: any | null;
screenshotUri?: string | null; screenshotUri?: string | null;
framesPerSecond?: number | null; framesPerSecond: number;
totalShots: number; totalShots: number;
owner?: { owner?: {
__typename?: "UserGQL"; __typename?: "UserGQL";
@@ -6778,12 +6724,6 @@ export type GetShotLabVideosQuery = {
username: string; username: string;
profileImageUri?: string | null; profileImageUri?: string | null;
} | null; } | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean;
lastSegmentUploadedAt?: any | null;
} | null;
}>; }>;
}; };
}; };
@@ -6807,13 +6747,29 @@ export type GetRecordedStreamShotsQuery = {
__typename?: "SerializedShotPathsGQL"; __typename?: "SerializedShotPathsGQL";
b64EncodedBuffer?: string | null; b64EncodedBuffer?: string | null;
} | null; } | null;
cueObjectFeatures?: {
__typename?: "CueObjectFeaturesGQL";
cueObjectDistance?: number | null;
cueObjectAngle?: number | null;
cueBallSpeed?: number | null;
shotDirection?: ShotDirectionEnum | null;
spinType?: SpinTypeEnum | null;
} | null;
pocketingIntentionFeatures?: { pocketingIntentionFeatures?: {
__typename?: "PocketingIntentionFeaturesGQL"; __typename?: "PocketingIntentionFeaturesGQL";
make?: boolean | null; make?: boolean | null;
targetPocketDistance?: number | null;
targetPocketAngle?: number | null;
targetPocketAngleDirection?: ShotDirectionEnum | null;
marginOfErrorInDegrees?: number | null;
intendedPocketType?: PocketEnum | null;
difficulty?: number | null;
} | null; } | null;
pocketingIntentionInfo?: { pocketingIntentionInfo?: {
__typename?: "PocketingIntentionInfoGQL"; __typename?: "PocketingIntentionInfoGQL";
ballId: number;
pocketId: PocketIdentifier; pocketId: PocketIdentifier;
pathMetadataIndex: number;
} | null; } | null;
}>; }>;
}; };
@@ -6971,102 +6927,6 @@ export type GetSessionCoachQuery = {
rankReason: string; rankReason: string;
}; };
}>; }>;
agentGeneration?: {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
supportingCandidateIds: Array<string>;
supportingShotIds: Array<number>;
sessionSummary?: 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;
synthesisRationale?: 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;
supportingCandidateIds: Array<string>;
supportingShotIds: Array<number>;
sessionSummary?: 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;
synthesisRationale?: string | null;
} | null;
} | null;
}; };
}; };
@@ -7099,49 +6959,6 @@ export type SessionCoachCandidateFieldsFragment = {
}; };
}; };
export type SessionCoachGenerationFieldsFragment = {
__typename?: "SessionCoachGenerationGQL";
id: string;
state: SessionCoachGenerationStateEnum;
createdAt: any;
completedAt?: any | null;
decision?: {
__typename?: "SessionCoachDecisionGQL";
selectedCandidateId?: string | null;
supportingCandidateIds: Array<string>;
supportingShotIds: Array<number>;
sessionSummary?: 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;
synthesisRationale?: string | null;
} | null;
};
export type PlayerSummaryFieldsFragment = { export type PlayerSummaryFieldsFragment = {
__typename?: "PlayerSummaryGQL"; __typename?: "PlayerSummaryGQL";
clusterId: number; clusterId: number;
@@ -8658,7 +8475,7 @@ export type GetVideosQuery = {
getVideos: Array<{ getVideos: Array<{
__typename?: "VideoGQL"; __typename?: "VideoGQL";
id: number; id: number;
framesPerSecond?: number | null; framesPerSecond: number;
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string; id: string;
@@ -8682,7 +8499,7 @@ export type GetVideosQuery = {
export type VideoStreamMetadataFragment = { export type VideoStreamMetadataFragment = {
__typename?: "VideoGQL"; __typename?: "VideoGQL";
id: number; id: number;
framesPerSecond?: number | null; framesPerSecond: number;
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string; id: string;
@@ -8711,7 +8528,7 @@ export type GetVideoForShotTimeQuery = {
getVideo: { getVideo: {
__typename?: "VideoGQL"; __typename?: "VideoGQL";
id: number; id: number;
framesPerSecond?: number | null; framesPerSecond: number;
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string; id: string;
@@ -8882,7 +8699,7 @@ export type PlaylistWithSegmentStartTimesFragment = {
export type VideoDurationDataFragment = { export type VideoDurationDataFragment = {
__typename?: "VideoGQL"; __typename?: "VideoGQL";
id: number; id: number;
framesPerSecond?: number | null; framesPerSecond: number;
playlist?: { playlist?: {
__typename?: "HLSPlaylistGQL"; __typename?: "HLSPlaylistGQL";
videoId: number; videoId: number;
@@ -8919,7 +8736,7 @@ export type GetVideoForClipTimesQuery = {
getVideo: { getVideo: {
__typename?: "VideoGQL"; __typename?: "VideoGQL";
id: number; id: number;
framesPerSecond?: number | null; framesPerSecond: number;
playlist?: { playlist?: {
__typename?: "HLSPlaylistGQL"; __typename?: "HLSPlaylistGQL";
videoId: number; videoId: number;
@@ -9214,6 +9031,7 @@ export type GetUploadLinkMutation = {
}; };
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string;
uploadCompletionCursor: number; uploadCompletionCursor: number;
} | null; } | null;
}; };
@@ -9312,6 +9130,7 @@ export type UploadStreamWithDetailsFragment = {
startTime?: any | null; startTime?: any | null;
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean; isCompleted: boolean;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number; uploadCompletionCursor: number;
@@ -9336,6 +9155,7 @@ export type GetUploadStreamsWithDetailsQuery = {
startTime?: any | null; startTime?: any | null;
stream?: { stream?: {
__typename?: "UploadStreamGQL"; __typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean; isCompleted: boolean;
lastIntendedSegmentBound?: number | null; lastIntendedSegmentBound?: number | null;
uploadCompletionCursor: number; uploadCompletionCursor: number;
@@ -9616,47 +9436,6 @@ export const SessionCoachCandidateFieldsFragmentDoc = gql`
} }
} }
`; `;
export const SessionCoachGenerationFieldsFragmentDoc = gql`
fragment SessionCoachGenerationFields on SessionCoachGenerationGQL {
id
state
createdAt
completedAt
decision {
selectedCandidateId
supportingCandidateIds
supportingShotIds
sessionSummary
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
synthesisRationale
}
}
`;
export const PlayerClusterShotFieldsFragmentDoc = gql` export const PlayerClusterShotFieldsFragmentDoc = gql`
fragment PlayerClusterShotFields on PlayerClusterShotGQL { fragment PlayerClusterShotFields on PlayerClusterShotGQL {
shotId shotId
@@ -9916,6 +9695,7 @@ export const UploadStreamWithDetailsFragmentDoc = gql`
name name
startTime startTime
stream { stream {
id
isCompleted isCompleted
lastIntendedSegmentBound lastIntendedSegmentBound
uploadCompletionCursor uploadCompletionCursor
@@ -11989,24 +11769,8 @@ export type GetVideoFeedQueryResult = Apollo.QueryResult<
GetVideoFeedQueryVariables GetVideoFeedQueryVariables
>; >;
export const GetMakesLeaderboardDocument = gql` export const GetMakesLeaderboardDocument = gql`
query GetMakesLeaderboard( query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
$interval: TimeInterval getMakesLeaderboard(interval: $interval, when: $when) {
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getMakesLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries { entries {
user { user {
id id
@@ -12035,11 +11799,6 @@ export const GetMakesLeaderboardDocument = gql`
* variables: { * variables: {
* interval: // value for 'interval' * interval: // value for 'interval'
* when: // value for 'when' * when: // value for 'when'
* gameType: // value for 'gameType'
* tableSizeMin: // value for 'tableSizeMin'
* tableSizeMax: // value for 'tableSizeMax'
* pocketSizeMin: // value for 'pocketSizeMin'
* pocketSizeMax: // value for 'pocketSizeMax'
* }, * },
* }); * });
*/ */
@@ -12093,24 +11852,8 @@ export type GetMakesLeaderboardQueryResult = Apollo.QueryResult<
GetMakesLeaderboardQueryVariables GetMakesLeaderboardQueryVariables
>; >;
export const GetRunsLeaderboardDocument = gql` export const GetRunsLeaderboardDocument = gql`
query GetRunsLeaderboard( query GetRunsLeaderboard($interval: TimeInterval, $when: DateTime) {
$interval: TimeInterval getLongestRunsLeaderboard(interval: $interval, when: $when) {
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getLongestRunsLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries { entries {
id id
runLength runLength
@@ -12142,11 +11885,6 @@ export const GetRunsLeaderboardDocument = gql`
* variables: { * variables: {
* interval: // value for 'interval' * interval: // value for 'interval'
* when: // value for 'when' * when: // value for 'when'
* gameType: // value for 'gameType'
* tableSizeMin: // value for 'tableSizeMin'
* tableSizeMax: // value for 'tableSizeMax'
* pocketSizeMin: // value for 'pocketSizeMin'
* pocketSizeMax: // value for 'pocketSizeMax'
* }, * },
* }); * });
*/ */
@@ -12394,6 +12132,105 @@ export type GetMyDrillRunsQueryResult = Apollo.QueryResult<
GetMyDrillRunsQuery, GetMyDrillRunsQuery,
GetMyDrillRunsQueryVariables GetMyDrillRunsQueryVariables
>; >;
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` export const GetVideoMakePercentageIntervalsDocument = gql`
query GetVideoMakePercentageIntervals( query GetVideoMakePercentageIntervals(
$videoId: ID! $videoId: ID!
@@ -14692,13 +14529,13 @@ export type ReactToVideoMutationOptions = Apollo.BaseMutationOptions<
ReactToVideoMutation, ReactToVideoMutation,
ReactToVideoMutationVariables ReactToVideoMutationVariables
>; >;
export const GetShotLabVideosDocument = gql` export const GetRecordedStreamsDocument = gql`
query GetShotLabVideos($userId: Int!, $limit: Int! = 25) { query GetRecordedStreams($limit: Int! = 25) {
getFeedVideos( getFeedVideos(
limit: $limit limit: $limit
includeCallersVideos: false filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE includePrivate: MINE
feedInput: { userId: $userId } feedInput: { allUsers: true }
) { ) {
videos { videos {
id id
@@ -14712,81 +14549,75 @@ export const GetShotLabVideosDocument = gql`
username username
profileImageUri profileImageUri
} }
stream {
id
isCompleted
lastSegmentUploadedAt
}
} }
} }
} }
`; `;
/** /**
* __useGetShotLabVideosQuery__ * __useGetRecordedStreamsQuery__
* *
* To run a query within a React component, call `useGetShotLabVideosQuery` and pass it any options that fit your needs. * To run a query within a React component, call `useGetRecordedStreamsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetShotLabVideosQuery` returns an object from Apollo Client that contains loading, error, and data properties * 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. * 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; * @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 * @example
* const { data, loading, error } = useGetShotLabVideosQuery({ * const { data, loading, error } = useGetRecordedStreamsQuery({
* variables: { * variables: {
* userId: // value for 'userId'
* limit: // value for 'limit' * limit: // value for 'limit'
* }, * },
* }); * });
*/ */
export function useGetShotLabVideosQuery( export function useGetRecordedStreamsQuery(
baseOptions: Apollo.QueryHookOptions< baseOptions?: Apollo.QueryHookOptions<
GetShotLabVideosQuery, GetRecordedStreamsQuery,
GetShotLabVideosQueryVariables GetRecordedStreamsQueryVariables
>, >,
) { ) {
const options = { ...defaultOptions, ...baseOptions }; const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<GetShotLabVideosQuery, GetShotLabVideosQueryVariables>( return Apollo.useQuery<
GetShotLabVideosDocument, GetRecordedStreamsQuery,
options, GetRecordedStreamsQueryVariables
); >(GetRecordedStreamsDocument, options);
} }
export function useGetShotLabVideosLazyQuery( export function useGetRecordedStreamsLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions< baseOptions?: Apollo.LazyQueryHookOptions<
GetShotLabVideosQuery, GetRecordedStreamsQuery,
GetShotLabVideosQueryVariables GetRecordedStreamsQueryVariables
>, >,
) { ) {
const options = { ...defaultOptions, ...baseOptions }; const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery< return Apollo.useLazyQuery<
GetShotLabVideosQuery, GetRecordedStreamsQuery,
GetShotLabVideosQueryVariables GetRecordedStreamsQueryVariables
>(GetShotLabVideosDocument, options); >(GetRecordedStreamsDocument, options);
} }
export function useGetShotLabVideosSuspenseQuery( export function useGetRecordedStreamsSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions< baseOptions?: Apollo.SuspenseQueryHookOptions<
GetShotLabVideosQuery, GetRecordedStreamsQuery,
GetShotLabVideosQueryVariables GetRecordedStreamsQueryVariables
>, >,
) { ) {
const options = { ...defaultOptions, ...baseOptions }; const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery< return Apollo.useSuspenseQuery<
GetShotLabVideosQuery, GetRecordedStreamsQuery,
GetShotLabVideosQueryVariables GetRecordedStreamsQueryVariables
>(GetShotLabVideosDocument, options); >(GetRecordedStreamsDocument, options);
} }
export type GetShotLabVideosQueryHookResult = ReturnType< export type GetRecordedStreamsQueryHookResult = ReturnType<
typeof useGetShotLabVideosQuery typeof useGetRecordedStreamsQuery
>; >;
export type GetShotLabVideosLazyQueryHookResult = ReturnType< export type GetRecordedStreamsLazyQueryHookResult = ReturnType<
typeof useGetShotLabVideosLazyQuery typeof useGetRecordedStreamsLazyQuery
>; >;
export type GetShotLabVideosSuspenseQueryHookResult = ReturnType< export type GetRecordedStreamsSuspenseQueryHookResult = ReturnType<
typeof useGetShotLabVideosSuspenseQuery typeof useGetRecordedStreamsSuspenseQuery
>; >;
export type GetShotLabVideosQueryResult = Apollo.QueryResult< export type GetRecordedStreamsQueryResult = Apollo.QueryResult<
GetShotLabVideosQuery, GetRecordedStreamsQuery,
GetShotLabVideosQueryVariables GetRecordedStreamsQueryVariables
>; >;
export const GetRecordedStreamShotsDocument = gql` export const GetRecordedStreamShotsDocument = gql`
query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) { query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) {
@@ -14803,11 +14634,26 @@ export const GetRecordedStreamShotsDocument = gql`
serializedShotPaths { serializedShotPaths {
b64EncodedBuffer b64EncodedBuffer
} }
cueObjectFeatures {
cueObjectDistance
cueObjectAngle
cueBallSpeed
shotDirection
spinType
}
pocketingIntentionFeatures { pocketingIntentionFeatures {
make make
targetPocketDistance
targetPocketAngle
targetPocketAngleDirection
marginOfErrorInDegrees
intendedPocketType
difficulty
} }
pocketingIntentionInfo { pocketingIntentionInfo {
ballId
pocketId pocketId
pathMetadataIndex
} }
} }
} }
@@ -15102,13 +14948,9 @@ export const GetSessionCoachDocument = gql`
candidates { candidates {
...SessionCoachCandidateFields ...SessionCoachCandidateFields
} }
agentGeneration {
...SessionCoachGenerationFields
}
} }
} }
${SessionCoachCandidateFieldsFragmentDoc} ${SessionCoachCandidateFieldsFragmentDoc}
${SessionCoachGenerationFieldsFragmentDoc}
`; `;
/** /**
@@ -15176,61 +15018,6 @@ export type GetSessionCoachQueryResult = Apollo.QueryResult<
GetSessionCoachQuery, GetSessionCoachQuery,
GetSessionCoachQueryVariables 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` export const VideoPlayerClustersDocument = gql`
query VideoPlayerClusters($videoId: Int!) { query VideoPlayerClusters($videoId: Int!) {
videoPlayerClusters(videoId: $videoId) { videoPlayerClusters(videoId: $videoId) {
@@ -19692,6 +19479,7 @@ export const GetUploadLinkDocument = gql`
} }
} }
stream { stream {
id
uploadCompletionCursor uploadCompletionCursor
} }
} }

View File

@@ -1,21 +1,5 @@
query GetMakesLeaderboard( query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
$interval: TimeInterval getMakesLeaderboard(interval: $interval, when: $when) {
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getMakesLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries { entries {
user { user {
id id
@@ -29,24 +13,8 @@ query GetMakesLeaderboard(
} }
} }
query GetRunsLeaderboard( query GetRunsLeaderboard($interval: TimeInterval, $when: DateTime) {
$interval: TimeInterval getLongestRunsLeaderboard(interval: $interval, when: $when) {
$when: DateTime
$gameType: String
$tableSizeMin: Float
$tableSizeMax: Float
$pocketSizeMin: Float
$pocketSizeMax: Float
) {
getLongestRunsLeaderboard(
interval: $interval
when: $when
gameType: $gameType
tableSizeMin: $tableSizeMin
tableSizeMax: $tableSizeMax
pocketSizeMin: $pocketSizeMin
pocketSizeMax: $pocketSizeMax
) {
entries { entries {
id id
runLength runLength

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

@@ -1,12 +1,10 @@
# Lightweight two-step picker data for Shot Lab's video-first source: the # Lightweight two-step picker data for loading a recorded shot into Shot Lab.
# caller's recent videos (completed or still uploading), then that video's query GetRecordedStreams($limit: Int! = 25) {
# shots. The stream fields hint at "actively updating" without gating.
query GetShotLabVideos($userId: Int!, $limit: Int! = 25) {
getFeedVideos( getFeedVideos(
limit: $limit limit: $limit
includeCallersVideos: false filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE includePrivate: MINE
feedInput: { userId: $userId } feedInput: { allUsers: true }
) { ) {
videos { videos {
id id
@@ -20,11 +18,6 @@ query GetShotLabVideos($userId: Int!, $limit: Int! = 25) {
username username
profileImageUri profileImageUri
} }
stream {
id
isCompleted
lastSegmentUploadedAt
}
} }
} }
} }
@@ -43,11 +36,26 @@ query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) {
serializedShotPaths { serializedShotPaths {
b64EncodedBuffer b64EncodedBuffer
} }
cueObjectFeatures {
cueObjectDistance
cueObjectAngle
cueBallSpeed
shotDirection
spinType
}
pocketingIntentionFeatures { pocketingIntentionFeatures {
make make
targetPocketDistance
targetPocketAngle
targetPocketAngleDirection
marginOfErrorInDegrees
intendedPocketType
difficulty
} }
pocketingIntentionInfo { pocketingIntentionInfo {
ballId
pocketId pocketId
pathMetadataIndex
} }
} }
} }

View File

@@ -20,18 +20,6 @@ query GetSessionCoach($videoId: Int!) {
candidates { candidates {
...SessionCoachCandidateFields ...SessionCoachCandidateFields
} }
agentGeneration {
...SessionCoachGenerationFields
}
}
}
mutation RequestSessionCoachGeneration($videoId: Int!) {
requestSessionCoachGeneration(videoId: $videoId) {
state
agentGeneration {
...SessionCoachGenerationFields
}
} }
} }
@@ -61,43 +49,3 @@ fragment SessionCoachCandidateFields on SessionCoachCandidateGQL {
rankReason rankReason
} }
} }
fragment SessionCoachGenerationFields on SessionCoachGenerationGQL {
id
state
createdAt
completedAt
decision {
selectedCandidateId
supportingCandidateIds
supportingShotIds
sessionSummary
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
synthesisRationale
}
}

View File

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

File diff suppressed because it is too large Load Diff