From d2c697e4cbad2123ff8cfce23c0efd710cc33a33 Mon Sep 17 00:00:00 2001 From: Dean Wenstrand Date: Tue, 30 Jun 2026 12:35:34 -0700 Subject: [PATCH 1/4] Add video export schema: VideoExportJob + request/query + mode/status enums Generated from the backend video export resolvers (Phase 1a). Co-Authored-By: Claude Opus 4.8 --- src/index.tsx | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/schema.gql | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/index.tsx b/src/index.tsx index 05a02be..a1d6260 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2440,6 +2440,7 @@ export type Mutation = { reactToVideo: Scalars["Boolean"]["output"]; recalculateChallengeEntry: ChallengeEntry; reportContent: Scalars["Boolean"]["output"]; + requestVideoExport: VideoExportJobGql; respondToChallengeInvitation: ChallengeInvitation; retireTags: Scalars["Boolean"]["output"]; revokeManualEntitlement: UserSubscriptionStatusGql; @@ -2613,6 +2614,10 @@ export type MutationReportContentArgs = { videoId: Scalars["Int"]["input"]; }; +export type MutationRequestVideoExportArgs = { + input: RequestVideoExportInput; +}; + export type MutationRespondToChallengeInvitationArgs = { accept: Scalars["Boolean"]["input"]; invitationId: Scalars["ID"]["input"]; @@ -2880,6 +2885,7 @@ export type Query = { notifications: NotificationConnection; ruleSets: Array; unreadNotificationCount: Scalars["Int"]["output"]; + videoExportJob?: Maybe; videoPlayerClusters: Array; waitFor: Scalars["Float"]["output"]; }; @@ -3062,6 +3068,10 @@ export type QueryNotificationsArgs = { offset?: Scalars["Int"]["input"]; }; +export type QueryVideoExportJobArgs = { + jobId: Scalars["Int"]["input"]; +}; + export type QueryVideoPlayerClustersArgs = { videoId: Scalars["Int"]["input"]; }; @@ -3121,6 +3131,13 @@ export enum ReportReasonEnum { Violence = "VIOLENCE", } +export type RequestVideoExportInput = { + mode: VideoExportModeEnum; + runId?: InputMaybe; + shotIds?: InputMaybe>; + videoId: Scalars["Int"]["input"]; +}; + export type RequestedMedalsGql = { __typename?: "RequestedMedalsGQL"; dailyMakes50?: Maybe; @@ -3648,6 +3665,33 @@ export type UserSubscriptionStatusGql = { validUntil?: Maybe; }; +export type VideoExportJobGql = { + __typename?: "VideoExportJobGQL"; + createdAt?: Maybe; + downloadUrl?: Maybe; + expiresAt?: Maybe; + fileSizeBytes?: Maybe; + id: Scalars["Int"]["output"]; + mode: VideoExportModeEnum; + status: VideoExportStatusEnum; + videoId: Scalars["Int"]["output"]; +}; + +export enum VideoExportModeEnum { + FullSession = "FULL_SESSION", + Run = "RUN", + Shots = "SHOTS", +} + +export enum VideoExportStatusEnum { + Created = "CREATED", + Expired = "EXPIRED", + Failed = "FAILED", + Queued = "QUEUED", + Running = "RUNNING", + Succeeded = "SUCCEEDED", +} + export type VideoFeedInputGql = | { allUsers: Scalars["Boolean"]["input"]; diff --git a/src/schema.gql b/src/schema.gql index 5115084..fd721f4 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -121,6 +121,7 @@ type Query { ): VideoHistoryGQL! getUserTags(includeRetiredTags: Boolean = false): [TagGQL!]! getGameTypeTagMetrics(input: GameTypeTagMetricsInput!): [GameTypeTagMetric!]! + videoExportJob(jobId: Int!): VideoExportJobGQL getVideo(videoId: Int!, debuggingJson: JSON = null): VideoGQL! getVideos(videoIds: [Int!]!): [VideoGQL!]! } @@ -1164,6 +1165,32 @@ input GameTypeTagMetricsInput { includePrivate: IncludePrivateEnum! = MINE } +type VideoExportJobGQL { + id: Int! + videoId: Int! + mode: VideoExportModeEnum! + status: VideoExportStatusEnum! + downloadUrl: String + fileSizeBytes: Int + expiresAt: DateTime + createdAt: DateTime +} + +enum VideoExportModeEnum { + FULL_SESSION + SHOTS + RUN +} + +enum VideoExportStatusEnum { + CREATED + QUEUED + RUNNING + SUCCEEDED + FAILED + EXPIRED +} + """ The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf). """ @@ -1265,6 +1292,7 @@ type Mutation { feedback: String = null metadata: CancellationFeedbackMetadataInput = null ): Boolean! + requestVideoExport(input: RequestVideoExportInput!): VideoExportJobGQL! findPrerecordTableLayout(b64Image: String!, videoId: Int!): HomographyInfoGQL createUploadStream( videoMetadata: VideoMetadataInput! @@ -1447,6 +1475,13 @@ input CancellationFeedbackMetadataInput { platform: String = null } +input RequestVideoExportInput { + videoId: Int! + mode: VideoExportModeEnum! + shotIds: [Int!] = null + runId: Int = null +} + type CreateUploadStreamReturn { videoId: Int! } From 3ec3e3d081b931211b6a97a0903b9ee38cb0d6f8 Mon Sep 17 00:00:00 2001 From: Dean Wenstrand Date: Tue, 30 Jun 2026 12:51:39 -0700 Subject: [PATCH 2/4] Add EXPORT_READY notification type (video export Phase 1b) Co-Authored-By: Claude Opus 4.8 --- src/index.tsx | 1 + src/schema.gql | 1 + 2 files changed, 2 insertions(+) diff --git a/src/index.tsx b/src/index.tsx index a1d6260..12aff2a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2711,6 +2711,7 @@ export enum NotificationTypeEnum { ChallengeInvite = "CHALLENGE_INVITE", Comment = "COMMENT", CommentReply = "COMMENT_REPLY", + ExportReady = "EXPORT_READY", Follow = "FOLLOW", Reaction = "REACTION", } diff --git a/src/schema.gql b/src/schema.gql index fd721f4..686714c 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -865,6 +865,7 @@ enum NotificationTypeEnum { REACTION FOLLOW CHALLENGE_INVITE + EXPORT_READY } input NotificationFilters { From f1594b8492e0c4e313192868d93a05f22edb186a Mon Sep 17 00:00:00 2001 From: Dean Wenstrand Date: Tue, 30 Jun 2026 13:54:29 -0700 Subject: [PATCH 3/4] Video export client operations + myVideoExports list query RequestVideoExport mutation, VideoExportJob + MyVideoExports queries (with the VideoExportJobFields fragment) for the mobile fire-and-list flow. Co-Authored-By: Claude Opus 4.8 --- src/index.tsx | 288 ++++++++++++++++++++++++++++++++ src/operations/video_export.gql | 28 ++++ src/schema.gql | 1 + 3 files changed, 317 insertions(+) create mode 100644 src/operations/video_export.gql diff --git a/src/index.tsx b/src/index.tsx index 12aff2a..9070f3a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2883,6 +2883,7 @@ export type Query = { myChallengeEntries: Array; myChallengeInvitations: Array; myDismissedChallenges: Array; + myVideoExports: Array; notifications: NotificationConnection; ruleSets: Array; unreadNotificationCount: Scalars["Int"]["output"]; @@ -3063,6 +3064,11 @@ export type QueryIsChallengeDismissedArgs = { challengeId: Scalars["ID"]["input"]; }; +export type QueryMyVideoExportsArgs = { + limit?: Scalars["Int"]["input"]; + offset?: Scalars["Int"]["input"]; +}; + export type QueryNotificationsArgs = { filters?: InputMaybe; limit?: Scalars["Int"]["input"]; @@ -7060,6 +7066,76 @@ export type HomographyInfoFragment = { }; }; +export type VideoExportJobFieldsFragment = { + __typename?: "VideoExportJobGQL"; + id: number; + videoId: number; + mode: VideoExportModeEnum; + status: VideoExportStatusEnum; + downloadUrl?: string | null; + fileSizeBytes?: number | null; + expiresAt?: any | null; + createdAt?: any | null; +}; + +export type RequestVideoExportMutationVariables = Exact<{ + input: RequestVideoExportInput; +}>; + +export type RequestVideoExportMutation = { + __typename?: "Mutation"; + requestVideoExport: { + __typename?: "VideoExportJobGQL"; + id: number; + videoId: number; + mode: VideoExportModeEnum; + status: VideoExportStatusEnum; + downloadUrl?: string | null; + fileSizeBytes?: number | null; + expiresAt?: any | null; + createdAt?: any | null; + }; +}; + +export type VideoExportJobQueryVariables = Exact<{ + jobId: Scalars["Int"]["input"]; +}>; + +export type VideoExportJobQuery = { + __typename?: "Query"; + videoExportJob?: { + __typename?: "VideoExportJobGQL"; + id: number; + videoId: number; + mode: VideoExportModeEnum; + status: VideoExportStatusEnum; + downloadUrl?: string | null; + fileSizeBytes?: number | null; + expiresAt?: any | null; + createdAt?: any | null; + } | null; +}; + +export type MyVideoExportsQueryVariables = Exact<{ + limit?: InputMaybe; + offset?: InputMaybe; +}>; + +export type MyVideoExportsQuery = { + __typename?: "Query"; + myVideoExports: Array<{ + __typename?: "VideoExportJobGQL"; + id: number; + videoId: number; + mode: VideoExportModeEnum; + status: VideoExportStatusEnum; + downloadUrl?: string | null; + fileSizeBytes?: number | null; + expiresAt?: any | null; + createdAt?: any | null; + }>; +}; + export type CreateUploadStreamMutationVariables = Exact<{ videoMetadataInput: VideoMetadataInput; expectedDurationSeconds?: InputMaybe; @@ -7625,6 +7701,18 @@ export const HomographyInfoFragmentDoc = gql` } } `; +export const VideoExportJobFieldsFragmentDoc = gql` + fragment VideoExportJobFields on VideoExportJobGQL { + id + videoId + mode + status + downloadUrl + fileSizeBytes + expiresAt + createdAt + } +`; export const UploadStreamWithDetailsFragmentDoc = gql` fragment UploadStreamWithDetails on VideoGQL { id @@ -15036,6 +15124,206 @@ export type FindPrerecordTableLayoutMutationOptions = FindPrerecordTableLayoutMutation, FindPrerecordTableLayoutMutationVariables >; +export const RequestVideoExportDocument = gql` + mutation RequestVideoExport($input: RequestVideoExportInput!) { + requestVideoExport(input: $input) { + ...VideoExportJobFields + } + } + ${VideoExportJobFieldsFragmentDoc} +`; +export type RequestVideoExportMutationFn = Apollo.MutationFunction< + RequestVideoExportMutation, + RequestVideoExportMutationVariables +>; + +/** + * __useRequestVideoExportMutation__ + * + * To run a mutation, you first call `useRequestVideoExportMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useRequestVideoExportMutation` 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 [requestVideoExportMutation, { data, loading, error }] = useRequestVideoExportMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useRequestVideoExportMutation( + baseOptions?: Apollo.MutationHookOptions< + RequestVideoExportMutation, + RequestVideoExportMutationVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useMutation< + RequestVideoExportMutation, + RequestVideoExportMutationVariables + >(RequestVideoExportDocument, options); +} +export type RequestVideoExportMutationHookResult = ReturnType< + typeof useRequestVideoExportMutation +>; +export type RequestVideoExportMutationResult = + Apollo.MutationResult; +export type RequestVideoExportMutationOptions = Apollo.BaseMutationOptions< + RequestVideoExportMutation, + RequestVideoExportMutationVariables +>; +export const VideoExportJobDocument = gql` + query VideoExportJob($jobId: Int!) { + videoExportJob(jobId: $jobId) { + ...VideoExportJobFields + } + } + ${VideoExportJobFieldsFragmentDoc} +`; + +/** + * __useVideoExportJobQuery__ + * + * To run a query within a React component, call `useVideoExportJobQuery` and pass it any options that fit your needs. + * When your component renders, `useVideoExportJobQuery` 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 } = useVideoExportJobQuery({ + * variables: { + * jobId: // value for 'jobId' + * }, + * }); + */ +export function useVideoExportJobQuery( + baseOptions: Apollo.QueryHookOptions< + VideoExportJobQuery, + VideoExportJobQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery( + VideoExportJobDocument, + options, + ); +} +export function useVideoExportJobLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + VideoExportJobQuery, + VideoExportJobQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery( + VideoExportJobDocument, + options, + ); +} +export function useVideoExportJobSuspenseQuery( + baseOptions?: Apollo.SuspenseQueryHookOptions< + VideoExportJobQuery, + VideoExportJobQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useSuspenseQuery< + VideoExportJobQuery, + VideoExportJobQueryVariables + >(VideoExportJobDocument, options); +} +export type VideoExportJobQueryHookResult = ReturnType< + typeof useVideoExportJobQuery +>; +export type VideoExportJobLazyQueryHookResult = ReturnType< + typeof useVideoExportJobLazyQuery +>; +export type VideoExportJobSuspenseQueryHookResult = ReturnType< + typeof useVideoExportJobSuspenseQuery +>; +export type VideoExportJobQueryResult = Apollo.QueryResult< + VideoExportJobQuery, + VideoExportJobQueryVariables +>; +export const MyVideoExportsDocument = gql` + query MyVideoExports($limit: Int = 30, $offset: Int = 0) { + myVideoExports(limit: $limit, offset: $offset) { + ...VideoExportJobFields + } + } + ${VideoExportJobFieldsFragmentDoc} +`; + +/** + * __useMyVideoExportsQuery__ + * + * To run a query within a React component, call `useMyVideoExportsQuery` and pass it any options that fit your needs. + * When your component renders, `useMyVideoExportsQuery` 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 } = useMyVideoExportsQuery({ + * variables: { + * limit: // value for 'limit' + * offset: // value for 'offset' + * }, + * }); + */ +export function useMyVideoExportsQuery( + baseOptions?: Apollo.QueryHookOptions< + MyVideoExportsQuery, + MyVideoExportsQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useQuery( + MyVideoExportsDocument, + options, + ); +} +export function useMyVideoExportsLazyQuery( + baseOptions?: Apollo.LazyQueryHookOptions< + MyVideoExportsQuery, + MyVideoExportsQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useLazyQuery( + MyVideoExportsDocument, + options, + ); +} +export function useMyVideoExportsSuspenseQuery( + baseOptions?: Apollo.SuspenseQueryHookOptions< + MyVideoExportsQuery, + MyVideoExportsQueryVariables + >, +) { + const options = { ...defaultOptions, ...baseOptions }; + return Apollo.useSuspenseQuery< + MyVideoExportsQuery, + MyVideoExportsQueryVariables + >(MyVideoExportsDocument, options); +} +export type MyVideoExportsQueryHookResult = ReturnType< + typeof useMyVideoExportsQuery +>; +export type MyVideoExportsLazyQueryHookResult = ReturnType< + typeof useMyVideoExportsLazyQuery +>; +export type MyVideoExportsSuspenseQueryHookResult = ReturnType< + typeof useMyVideoExportsSuspenseQuery +>; +export type MyVideoExportsQueryResult = Apollo.QueryResult< + MyVideoExportsQuery, + MyVideoExportsQueryVariables +>; export const CreateUploadStreamDocument = gql` mutation CreateUploadStream( $videoMetadataInput: VideoMetadataInput! diff --git a/src/operations/video_export.gql b/src/operations/video_export.gql new file mode 100644 index 0000000..b65d0b8 --- /dev/null +++ b/src/operations/video_export.gql @@ -0,0 +1,28 @@ +fragment VideoExportJobFields on VideoExportJobGQL { + id + videoId + mode + status + downloadUrl + fileSizeBytes + expiresAt + createdAt +} + +mutation RequestVideoExport($input: RequestVideoExportInput!) { + requestVideoExport(input: $input) { + ...VideoExportJobFields + } +} + +query VideoExportJob($jobId: Int!) { + videoExportJob(jobId: $jobId) { + ...VideoExportJobFields + } +} + +query MyVideoExports($limit: Int = 30, $offset: Int = 0) { + myVideoExports(limit: $limit, offset: $offset) { + ...VideoExportJobFields + } +} diff --git a/src/schema.gql b/src/schema.gql index 686714c..c2dfb10 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -122,6 +122,7 @@ type Query { getUserTags(includeRetiredTags: Boolean = false): [TagGQL!]! getGameTypeTagMetrics(input: GameTypeTagMetricsInput!): [GameTypeTagMetric!]! videoExportJob(jobId: Int!): VideoExportJobGQL + myVideoExports(limit: Int! = 30, offset: Int! = 0): [VideoExportJobGQL!]! getVideo(videoId: Int!, debuggingJson: JSON = null): VideoGQL! getVideos(videoIds: [Int!]!): [VideoGQL!]! } From 8771350115eeb21d395cdb23d67c98e2824ed248 Mon Sep 17 00:00:00 2001 From: Dean Wenstrand Date: Tue, 30 Jun 2026 14:00:04 -0700 Subject: [PATCH 4/4] Echo shotIds/runId on VideoExportJobGQL for re-export Co-Authored-By: Claude Opus 4.8 --- src/index.tsx | 12 ++++++++++++ src/operations/video_export.gql | 2 ++ src/schema.gql | 2 ++ 3 files changed, 16 insertions(+) diff --git a/src/index.tsx b/src/index.tsx index 9070f3a..8401e87 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -3680,6 +3680,8 @@ export type VideoExportJobGql = { fileSizeBytes?: Maybe; id: Scalars["Int"]["output"]; mode: VideoExportModeEnum; + runId?: Maybe; + shotIds?: Maybe>; status: VideoExportStatusEnum; videoId: Scalars["Int"]["output"]; }; @@ -7072,6 +7074,8 @@ export type VideoExportJobFieldsFragment = { videoId: number; mode: VideoExportModeEnum; status: VideoExportStatusEnum; + shotIds?: Array | null; + runId?: number | null; downloadUrl?: string | null; fileSizeBytes?: number | null; expiresAt?: any | null; @@ -7090,6 +7094,8 @@ export type RequestVideoExportMutation = { videoId: number; mode: VideoExportModeEnum; status: VideoExportStatusEnum; + shotIds?: Array | null; + runId?: number | null; downloadUrl?: string | null; fileSizeBytes?: number | null; expiresAt?: any | null; @@ -7109,6 +7115,8 @@ export type VideoExportJobQuery = { videoId: number; mode: VideoExportModeEnum; status: VideoExportStatusEnum; + shotIds?: Array | null; + runId?: number | null; downloadUrl?: string | null; fileSizeBytes?: number | null; expiresAt?: any | null; @@ -7129,6 +7137,8 @@ export type MyVideoExportsQuery = { videoId: number; mode: VideoExportModeEnum; status: VideoExportStatusEnum; + shotIds?: Array | null; + runId?: number | null; downloadUrl?: string | null; fileSizeBytes?: number | null; expiresAt?: any | null; @@ -7707,6 +7717,8 @@ export const VideoExportJobFieldsFragmentDoc = gql` videoId mode status + shotIds + runId downloadUrl fileSizeBytes expiresAt diff --git a/src/operations/video_export.gql b/src/operations/video_export.gql index b65d0b8..86a1fb2 100644 --- a/src/operations/video_export.gql +++ b/src/operations/video_export.gql @@ -3,6 +3,8 @@ fragment VideoExportJobFields on VideoExportJobGQL { videoId mode status + shotIds + runId downloadUrl fileSizeBytes expiresAt diff --git a/src/schema.gql b/src/schema.gql index c2dfb10..f87bd38 100644 --- a/src/schema.gql +++ b/src/schema.gql @@ -1172,6 +1172,8 @@ type VideoExportJobGQL { videoId: Int! mode: VideoExportModeEnum! status: VideoExportStatusEnum! + shotIds: [Int!] + runId: Int downloadUrl: String fileSizeBytes: Int expiresAt: DateTime