Compare commits

..

11 Commits

Author SHA1 Message Date
Dean Wenstrand
aa731b85fc Declare taxonomy filter variables on the makes/runs leaderboards
All checks were successful
Tests / Tests (pull_request) Successful in 18s
The backend leaderboard resolvers gained game_type, table size and pocket
size filters, but GetMakesLeaderboard and GetRunsLeaderboard never declared
them — GraphQL silently drops undeclared variables, so anything the client
passed was ignored and both boards stayed global.

Declare and forward the five filter args on both queries, matching the
names the drill run leaderboard already uses.
2026-07-21 09:30:37 -07:00
1a95439ed2 Replace GetRecordedStreams/GetLiveStreams with video-first GetShotLabVideos
All checks were successful
Tests / Tests (pull_request) Successful in 44s
Shot Lab's source picker becomes video-first: one query for recent videos
regardless of stream completion (an in-progress recording with no shots
yet still appears), carrying stream recency fields so the client can hint
"actively updating" without gating on it. Operation-only change; schema
is untouched (the stream fields already exist).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 23:42:43 -07:00
5e14cd2201 Merge pull request 'Regenerate schema for leaderboard taxonomy filters' (#300) from dean/leaderboard-taxonomy into master
Reviewed-on: #300
2026-07-21 04:20:57 +00:00
Dean Wenstrand
9ec52d9d94 Regenerate schema for leaderboard taxonomy filters
All checks were successful
Tests / Tests (pull_request) Successful in 11s
Adds the optional taxonomy args to getLongestRunsLeaderboard and
getMakesLeaderboard (gameType, rankedOnly, excludeDrills, and
table/pocket size bounds) generated from the backend resolvers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 21:19:39 -07:00
4576b25dae Merge pull request 'Regenerate schema with upstream Strawberry' (#299) from codex/strawberry-upgrade into master 2026-07-20 22:08:35 +00:00
650b3bc13c Regenerate schema with upstream Strawberry
All checks were successful
Tests / Tests (pull_request) Successful in 11s
2026-07-20 14:46:00 -07:00
6d9cb4126e Merge pull request 'Add per-video HomographyBackendGQL selection to VideoMetadataInput' (#298) from colonelpanic/homography-backend-selection into master 2026-07-18 02:12:18 +00:00
feeb5b7038 Add HomographyBackendGQL enum and VideoMetadataInput.homographyBackend
All checks were successful
Tests / Tests (pull_request) Successful in 11s
Regenerated from backend Python: adds the HomographyBackendGQL enum
(CLASSIC/TABLENET) and the optional homographyBackend field on
VideoMetadataInput, for per-video homography backend selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 18:26:53 -07:00
3340314166 Merge pull request 'Add per-session Coach GraphQL contract' (#297) from loewy/session-coach-v1 into master
Reviewed-on: #297
2026-07-17 18:33:58 +00:00
6b8fdd4262 Add per-session Coach GraphQL contract
All checks were successful
Tests / Tests (pull_request) Successful in 10s
2026-07-16 16:45:55 -07:00
bfaed33c89 Merge pull request 'feed: select uploadCompletionCursor on VideoCardFields stream' (#296) from dean/feed-upload-completion-cursor into master
Reviewed-on: #296
2026-07-14 18:24:54 +00:00
6 changed files with 2148 additions and 1734 deletions

View File

@@ -2404,6 +2404,11 @@ export type Header = {
value: Scalars["String"]["output"];
};
export enum HomographyBackendGql {
Classic = "CLASSIC",
Tablenet = "TABLENET",
}
export type HomographyInfoGql = {
__typename?: "HomographyInfoGQL";
crop: BoundingBoxGql;
@@ -3088,6 +3093,7 @@ export type Query = {
getQuotaStatus: QuotaStatusGql;
getResolvedTier: ResolvedTierGql;
getRuns: GetRunsResult;
getSessionCoach: SessionCoachGql;
getShotAnnotationTypes: Array<ShotAnnotationTypeGql>;
getShotTableState: ShotTableStateGql;
getShots: Array<ShotGql>;
@@ -3189,14 +3195,28 @@ export type QueryGetLiveTableStateArgs = {
};
export type QueryGetLongestRunsLeaderboardArgs = {
excludeDrills?: Scalars["Boolean"]["input"];
gameType?: InputMaybe<Scalars["String"]["input"]>;
interval?: InputMaybe<TimeInterval>;
limit?: Scalars["Int"]["input"];
pocketSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
pocketSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
rankedOnly?: Scalars["Boolean"]["input"];
requiredTags?: InputMaybe<Array<Scalars["String"]["input"]>>;
tableSizeMax?: InputMaybe<Scalars["Float"]["input"]>;
tableSizeMin?: InputMaybe<Scalars["Float"]["input"]>;
when?: InputMaybe<Scalars["DateTime"]["input"]>;
};
export type QueryGetMakesLeaderboardArgs = {
excludeDrills?: Scalars["Boolean"]["input"];
gameType?: InputMaybe<Scalars["String"]["input"]>;
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"]>;
};
@@ -3231,6 +3251,10 @@ export type QueryGetRunsArgs = {
runsOrdering?: InputMaybe<GetRunsOrdering>;
};
export type QueryGetSessionCoachArgs = {
videoId: Scalars["Int"]["input"];
};
export type QueryGetShotAnnotationTypesArgs = {
errorTypes?: InputMaybe<Scalars["Boolean"]["input"]>;
};
@@ -3546,6 +3570,84 @@ export type SerializedShotPathsGql = {
b64EncodedBuffer?: Maybe<Scalars["String"]["output"]>;
};
export enum SessionCoachCandidateFamilyEnum {
Aiming = "AIMING",
BackCut = "BACK_CUT",
CueToObjectDistance = "CUE_TO_OBJECT_DISTANCE",
CutAngle = "CUT_ANGLE",
ObjectToPocketDistance = "OBJECT_TO_POCKET_DISTANCE",
Spin = "SPIN",
}
export type SessionCoachCandidateGql = {
__typename?: "SessionCoachCandidateGQL";
action: Scalars["String"]["output"];
body: Scalars["String"]["output"];
drillId?: Maybe<Scalars["String"]["output"]>;
evidence: SessionCoachEvidenceGql;
family: SessionCoachCandidateFamilyEnum;
id: Scalars["ID"]["output"];
kind: SessionCoachCandidateKindEnum;
limitations: Array<Scalars["String"]["output"]>;
rank: Scalars["Int"]["output"];
supportingShotIds: Array<Scalars["Int"]["output"]>;
title: Scalars["String"]["output"];
};
export enum SessionCoachCandidateKindEnum {
DirectionalMiss = "DIRECTIONAL_MISS",
OutcomePattern = "OUTCOME_PATTERN",
UsagePattern = "USAGE_PATTERN",
}
export type SessionCoachEvidenceGql = {
__typename?: "SessionCoachEvidenceGQL";
attemptCount: Scalars["Int"]["output"];
bucketKey: Scalars["String"]["output"];
bucketLabel: Scalars["String"]["output"];
comparisonAttemptCount: Scalars["Int"]["output"];
comparisonMadeCount: Scalars["Int"]["output"];
comparisonMakePercentage?: Maybe<Scalars["Float"]["output"]>;
madeCount: Scalars["Int"]["output"];
makePercentage: Scalars["Float"]["output"];
missedCount: Scalars["Int"]["output"];
patternShotCount: Scalars["Int"]["output"];
rankReason: Scalars["String"]["output"];
score: Scalars["Float"]["output"];
};
export type SessionCoachGql = {
__typename?: "SessionCoachGQL";
analysisVersion: Scalars["String"]["output"];
analyzedShotCount: Scalars["Int"]["output"];
candidates: Array<SessionCoachCandidateGql>;
generatedAt: Scalars["DateTime"]["output"];
ineligibilityReason?: Maybe<SessionCoachIneligibilityReasonEnum>;
madeCount: Scalars["Int"]["output"];
makePercentage?: Maybe<Scalars["Float"]["output"]>;
missedCount: Scalars["Int"]["output"];
primaryCandidate?: Maybe<SessionCoachCandidateGql>;
processingId?: Maybe<Scalars["Int"]["output"]>;
processingStatus?: Maybe<Scalars["String"]["output"]>;
shotCount: Scalars["Int"]["output"];
state: SessionCoachStateEnum;
summary?: Maybe<Scalars["String"]["output"]>;
unknownOutcomeCount: Scalars["Int"]["output"];
videoId: Scalars["Int"]["output"];
};
export enum SessionCoachIneligibilityReasonEnum {
Multiplayer = "MULTIPLAYER",
}
export enum SessionCoachStateEnum {
Failed = "FAILED",
Ineligible = "INELIGIBLE",
NoTrackedShots = "NO_TRACKED_SHOTS",
Processing = "PROCESSING",
Ready = "READY",
}
export type ShotAnnotationGql = {
__typename?: "ShotAnnotationGQL";
createdAt?: Maybe<Scalars["DateTime"]["output"]>;
@@ -4195,6 +4297,7 @@ export type VideoMetadataInput = {
framesPerSecond?: InputMaybe<Scalars["Float"]["input"]>;
/** @deprecated `game_type` is deprecated. Use `tags` instead. */
gameType?: InputMaybe<Scalars["String"]["input"]>;
homographyBackend?: InputMaybe<HomographyBackendGql>;
lastIntendedSegmentBound?: InputMaybe<Scalars["Int"]["input"]>;
pocketSize?: InputMaybe<Scalars["Float"]["input"]>;
private?: InputMaybe<Scalars["Boolean"]["input"]>;
@@ -5133,6 +5236,11 @@ export type GetVideoFeedQuery = {
export type GetMakesLeaderboardQueryVariables = Exact<{
interval?: InputMaybe<TimeInterval>;
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 = {
@@ -5157,6 +5265,11 @@ export type GetMakesLeaderboardQuery = {
export type GetRunsLeaderboardQueryVariables = Exact<{
interval?: InputMaybe<TimeInterval>;
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 = {
@@ -5241,43 +5354,6 @@ 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<{
videoId: Scalars["ID"]["input"];
intervalDuration: Scalars["Int"]["input"];
@@ -6619,11 +6695,11 @@ export type ReactToVideoMutation = {
reactToVideo: boolean;
};
export type GetRecordedStreamsQueryVariables = Exact<{
export type GetShotLabVideosQueryVariables = Exact<{
limit?: Scalars["Int"]["input"];
}>;
export type GetRecordedStreamsQuery = {
export type GetShotLabVideosQuery = {
__typename?: "Query";
getFeedVideos: {
__typename?: "VideoHistoryGQL";
@@ -6641,6 +6717,12 @@ export type GetRecordedStreamsQuery = {
username: string;
profileImageUri?: string | null;
} | null;
stream?: {
__typename?: "UploadStreamGQL";
id: string;
isCompleted: boolean;
lastSegmentUploadedAt?: any | null;
} | null;
}>;
};
};
@@ -6766,6 +6848,116 @@ export type GetRunsWithTimestampsQuery = {
};
};
export type GetSessionCoachQueryVariables = Exact<{
videoId: Scalars["Int"]["input"];
}>;
export type GetSessionCoachQuery = {
__typename?: "Query";
getSessionCoach: {
__typename?: "SessionCoachGQL";
videoId: number;
processingId?: number | null;
analysisVersion: string;
state: SessionCoachStateEnum;
generatedAt: any;
processingStatus?: string | null;
ineligibilityReason?: SessionCoachIneligibilityReasonEnum | null;
summary?: string | null;
shotCount: number;
madeCount: number;
missedCount: number;
unknownOutcomeCount: number;
analyzedShotCount: number;
makePercentage?: number | null;
primaryCandidate?: {
__typename?: "SessionCoachCandidateGQL";
id: string;
rank: number;
family: SessionCoachCandidateFamilyEnum;
kind: SessionCoachCandidateKindEnum;
title: string;
body: string;
action: string;
supportingShotIds: Array<number>;
drillId?: string | null;
limitations: Array<string>;
evidence: {
__typename?: "SessionCoachEvidenceGQL";
bucketKey: string;
bucketLabel: string;
attemptCount: number;
madeCount: number;
missedCount: number;
makePercentage: number;
comparisonAttemptCount: number;
comparisonMadeCount: number;
comparisonMakePercentage?: number | null;
patternShotCount: number;
score: number;
rankReason: string;
};
} | null;
candidates: Array<{
__typename?: "SessionCoachCandidateGQL";
id: string;
rank: number;
family: SessionCoachCandidateFamilyEnum;
kind: SessionCoachCandidateKindEnum;
title: string;
body: string;
action: string;
supportingShotIds: Array<number>;
drillId?: string | null;
limitations: Array<string>;
evidence: {
__typename?: "SessionCoachEvidenceGQL";
bucketKey: string;
bucketLabel: string;
attemptCount: number;
madeCount: number;
missedCount: number;
makePercentage: number;
comparisonAttemptCount: number;
comparisonMadeCount: number;
comparisonMakePercentage?: number | null;
patternShotCount: number;
score: number;
rankReason: string;
};
}>;
};
};
export type SessionCoachCandidateFieldsFragment = {
__typename?: "SessionCoachCandidateGQL";
id: string;
rank: number;
family: SessionCoachCandidateFamilyEnum;
kind: SessionCoachCandidateKindEnum;
title: string;
body: string;
action: string;
supportingShotIds: Array<number>;
drillId?: string | null;
limitations: Array<string>;
evidence: {
__typename?: "SessionCoachEvidenceGQL";
bucketKey: string;
bucketLabel: string;
attemptCount: number;
madeCount: number;
missedCount: number;
makePercentage: number;
comparisonAttemptCount: number;
comparisonMadeCount: number;
comparisonMakePercentage?: number | null;
patternShotCount: number;
score: number;
rankReason: string;
};
};
export type PlayerSummaryFieldsFragment = {
__typename?: "PlayerSummaryGQL";
clusterId: number;
@@ -9212,6 +9404,34 @@ export const PocketingIntentionFragmentFragmentDoc = gql`
difficulty
}
`;
export const SessionCoachCandidateFieldsFragmentDoc = gql`
fragment SessionCoachCandidateFields on SessionCoachCandidateGQL {
id
rank
family
kind
title
body
action
supportingShotIds
drillId
limitations
evidence {
bucketKey
bucketLabel
attemptCount
madeCount
missedCount
makePercentage
comparisonAttemptCount
comparisonMadeCount
comparisonMakePercentage
patternShotCount
score
rankReason
}
}
`;
export const PlayerClusterShotFieldsFragmentDoc = gql`
fragment PlayerClusterShotFields on PlayerClusterShotGQL {
shotId
@@ -11544,8 +11764,24 @@ export type GetVideoFeedQueryResult = Apollo.QueryResult<
GetVideoFeedQueryVariables
>;
export const GetMakesLeaderboardDocument = gql`
query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
getMakesLeaderboard(interval: $interval, when: $when) {
query GetMakesLeaderboard(
$interval: TimeInterval
$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 {
user {
id
@@ -11574,6 +11810,11 @@ export const GetMakesLeaderboardDocument = gql`
* variables: {
* interval: // value for 'interval'
* when: // value for 'when'
* gameType: // value for 'gameType'
* tableSizeMin: // value for 'tableSizeMin'
* tableSizeMax: // value for 'tableSizeMax'
* pocketSizeMin: // value for 'pocketSizeMin'
* pocketSizeMax: // value for 'pocketSizeMax'
* },
* });
*/
@@ -11627,8 +11868,24 @@ export type GetMakesLeaderboardQueryResult = Apollo.QueryResult<
GetMakesLeaderboardQueryVariables
>;
export const GetRunsLeaderboardDocument = gql`
query GetRunsLeaderboard($interval: TimeInterval, $when: DateTime) {
getLongestRunsLeaderboard(interval: $interval, when: $when) {
query GetRunsLeaderboard(
$interval: TimeInterval
$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 {
id
runLength
@@ -11660,6 +11917,11 @@ export const GetRunsLeaderboardDocument = gql`
* variables: {
* interval: // value for 'interval'
* when: // value for 'when'
* gameType: // value for 'gameType'
* tableSizeMin: // value for 'tableSizeMin'
* tableSizeMax: // value for 'tableSizeMax'
* pocketSizeMin: // value for 'pocketSizeMin'
* pocketSizeMax: // value for 'pocketSizeMax'
* },
* });
*/
@@ -11907,105 +12169,6 @@ export type GetMyDrillRunsQueryResult = Apollo.QueryResult<
GetMyDrillRunsQuery,
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`
query GetVideoMakePercentageIntervals(
$videoId: ID!
@@ -14304,11 +14467,10 @@ export type ReactToVideoMutationOptions = Apollo.BaseMutationOptions<
ReactToVideoMutation,
ReactToVideoMutationVariables
>;
export const GetRecordedStreamsDocument = gql`
query GetRecordedStreams($limit: Int! = 25) {
export const GetShotLabVideosDocument = gql`
query GetShotLabVideos($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE
feedInput: { allUsers: true }
) {
@@ -14324,75 +14486,80 @@ export const GetRecordedStreamsDocument = gql`
username
profileImageUri
}
stream {
id
isCompleted
lastSegmentUploadedAt
}
}
}
}
`;
/**
* __useGetRecordedStreamsQuery__
* __useGetShotLabVideosQuery__
*
* 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
* To run a query within a React component, call `useGetShotLabVideosQuery` 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
* 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({
* const { data, loading, error } = useGetShotLabVideosQuery({
* variables: {
* limit: // value for 'limit'
* },
* });
*/
export function useGetRecordedStreamsQuery(
export function useGetShotLabVideosQuery(
baseOptions?: Apollo.QueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
GetShotLabVideosQuery,
GetShotLabVideosQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
return Apollo.useQuery<GetShotLabVideosQuery, GetShotLabVideosQueryVariables>(
GetShotLabVideosDocument,
options,
);
}
export function useGetRecordedStreamsLazyQuery(
export function useGetShotLabVideosLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
GetShotLabVideosQuery,
GetShotLabVideosQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
GetShotLabVideosQuery,
GetShotLabVideosQueryVariables
>(GetShotLabVideosDocument, options);
}
export function useGetRecordedStreamsSuspenseQuery(
export function useGetShotLabVideosSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
GetShotLabVideosQuery,
GetShotLabVideosQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
>(GetRecordedStreamsDocument, options);
GetShotLabVideosQuery,
GetShotLabVideosQueryVariables
>(GetShotLabVideosDocument, options);
}
export type GetRecordedStreamsQueryHookResult = ReturnType<
typeof useGetRecordedStreamsQuery
export type GetShotLabVideosQueryHookResult = ReturnType<
typeof useGetShotLabVideosQuery
>;
export type GetRecordedStreamsLazyQueryHookResult = ReturnType<
typeof useGetRecordedStreamsLazyQuery
export type GetShotLabVideosLazyQueryHookResult = ReturnType<
typeof useGetShotLabVideosLazyQuery
>;
export type GetRecordedStreamsSuspenseQueryHookResult = ReturnType<
typeof useGetRecordedStreamsSuspenseQuery
export type GetShotLabVideosSuspenseQueryHookResult = ReturnType<
typeof useGetShotLabVideosSuspenseQuery
>;
export type GetRecordedStreamsQueryResult = Apollo.QueryResult<
GetRecordedStreamsQuery,
GetRecordedStreamsQueryVariables
export type GetShotLabVideosQueryResult = Apollo.QueryResult<
GetShotLabVideosQuery,
GetShotLabVideosQueryVariables
>;
export const GetRecordedStreamShotsDocument = gql`
query GetRecordedStreamShots($videoId: Int!, $limit: Int! = 200) {
@@ -14700,6 +14867,99 @@ export type GetRunsWithTimestampsQueryResult = Apollo.QueryResult<
GetRunsWithTimestampsQuery,
GetRunsWithTimestampsQueryVariables
>;
export const GetSessionCoachDocument = gql`
query GetSessionCoach($videoId: Int!) {
getSessionCoach(videoId: $videoId) {
videoId
processingId
analysisVersion
state
generatedAt
processingStatus
ineligibilityReason
summary
shotCount
madeCount
missedCount
unknownOutcomeCount
analyzedShotCount
makePercentage
primaryCandidate {
...SessionCoachCandidateFields
}
candidates {
...SessionCoachCandidateFields
}
}
}
${SessionCoachCandidateFieldsFragmentDoc}
`;
/**
* __useGetSessionCoachQuery__
*
* To run a query within a React component, call `useGetSessionCoachQuery` and pass it any options that fit your needs.
* When your component renders, `useGetSessionCoachQuery` 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 } = useGetSessionCoachQuery({
* variables: {
* videoId: // value for 'videoId'
* },
* });
*/
export function useGetSessionCoachQuery(
baseOptions: Apollo.QueryHookOptions<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<GetSessionCoachQuery, GetSessionCoachQueryVariables>(
GetSessionCoachDocument,
options,
);
}
export function useGetSessionCoachLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>(GetSessionCoachDocument, options);
}
export function useGetSessionCoachSuspenseQuery(
baseOptions?: Apollo.SuspenseQueryHookOptions<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>,
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useSuspenseQuery<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>(GetSessionCoachDocument, options);
}
export type GetSessionCoachQueryHookResult = ReturnType<
typeof useGetSessionCoachQuery
>;
export type GetSessionCoachLazyQueryHookResult = ReturnType<
typeof useGetSessionCoachLazyQuery
>;
export type GetSessionCoachSuspenseQueryHookResult = ReturnType<
typeof useGetSessionCoachSuspenseQuery
>;
export type GetSessionCoachQueryResult = Apollo.QueryResult<
GetSessionCoachQuery,
GetSessionCoachQueryVariables
>;
export const VideoPlayerClustersDocument = gql`
query VideoPlayerClusters($videoId: Int!) {
videoPlayerClusters(videoId: $videoId) {

View File

@@ -1,5 +1,21 @@
query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
getMakesLeaderboard(interval: $interval, when: $when) {
query GetMakesLeaderboard(
$interval: TimeInterval
$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 {
user {
id
@@ -13,8 +29,24 @@ query GetMakesLeaderboard($interval: TimeInterval, $when: DateTime) {
}
}
query GetRunsLeaderboard($interval: TimeInterval, $when: DateTime) {
getLongestRunsLeaderboard(interval: $interval, when: $when) {
query GetRunsLeaderboard(
$interval: TimeInterval
$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 {
id
runLength

View File

@@ -1,34 +0,0 @@
# 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,8 +1,10 @@
# Lightweight two-step picker data for loading a recorded shot into Shot Lab.
query GetRecordedStreams($limit: Int! = 25) {
# Lightweight two-step picker data for Shot Lab's video-first source: recent
# videos (completed or still uploading — no completion gate, so an in-progress
# recording with no shots yet still appears), then that video's shots. The
# stream fields let the client hint at "actively updating" without gating.
query GetShotLabVideos($limit: Int! = 25) {
getFeedVideos(
limit: $limit
filters: { isStreamCompleted: true, excludeVideosWithNoShots: true }
includePrivate: MINE
feedInput: { allUsers: true }
) {
@@ -18,6 +20,11 @@ query GetRecordedStreams($limit: Int! = 25) {
username
profileImageUri
}
stream {
id
isCompleted
lastSegmentUploadedAt
}
}
}
}

View File

@@ -0,0 +1,51 @@
query GetSessionCoach($videoId: Int!) {
getSessionCoach(videoId: $videoId) {
videoId
processingId
analysisVersion
state
generatedAt
processingStatus
ineligibilityReason
summary
shotCount
madeCount
missedCount
unknownOutcomeCount
analyzedShotCount
makePercentage
primaryCandidate {
...SessionCoachCandidateFields
}
candidates {
...SessionCoachCandidateFields
}
}
}
fragment SessionCoachCandidateFields on SessionCoachCandidateGQL {
id
rank
family
kind
title
body
action
supportingShotIds
drillId
limitations
evidence {
bucketKey
bucketLabel
attemptCount
madeCount
missedCount
makePercentage
comparisonAttemptCount
comparisonMadeCount
comparisonMakePercentage
patternShotCount
score
rankReason
}
}

File diff suppressed because it is too large Load Diff