Compare commits

..

1 Commits

Author SHA1 Message Date
9b6b08c1ec Return the shot's start frame image from getShotTableState
All checks were successful
Tests / Tests (pull_request) Successful in 12s
Shot Lab was showing the video's single thumbnail next to every shot's
layout. The backend now returns the frame the layout was detected in as
a base64 JPEG (frameImage), selected by the GetShotTableState operation.
Also dedupes ShareLinkGQL/CreateShotShareLinkInput, which appeared twice
in the committed schema after a bad merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 09:56:38 -07:00
3 changed files with 11 additions and 101 deletions

View File

@@ -476,7 +476,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -521,7 +520,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -566,7 +564,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -611,7 +608,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -656,7 +652,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -701,7 +696,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -746,7 +740,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -791,7 +784,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -836,7 +828,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -881,7 +872,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -926,7 +916,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -971,7 +960,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1016,7 +1004,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1061,7 +1048,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1106,7 +1092,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1151,7 +1136,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1196,7 +1180,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1241,7 +1224,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1286,7 +1268,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1331,7 +1312,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1376,7 +1356,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1421,7 +1400,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1466,7 +1444,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1511,7 +1488,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1556,7 +1532,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1601,7 +1576,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1646,7 +1620,6 @@ export type FilterInput =
missAngleInDegrees: FloatRangeFilter; missAngleInDegrees: FloatRangeFilter;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1691,7 +1664,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter: FilterInput; notFilter: FilterInput;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1736,7 +1708,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters: Array<FilterInput>; orFilters: Array<FilterInput>;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1781,52 +1752,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex: Array<Scalars["Int"]["input"]>;
runId?: never;
runLength?: never;
shotDirection?: never;
spinType?: never;
tableSize?: never;
tags?: never;
targetPocketAngle?: never;
targetPocketAngleDirection?: never;
targetPocketDistance?: never;
totalDistance?: never;
userId?: never;
username?: never;
videoId?: never;
}
| {
andFilters?: never;
annotations?: never;
backcut?: never;
bankAngle?: never;
bankDistance?: never;
createdAt?: never;
cueAngleAfterObject?: never;
cueBallSpeed?: never;
cueObjectAngle?: never;
cueObjectDistance?: never;
cueSpeedAfterObject?: never;
difficulty?: never;
falsePositiveScore?: never;
fargoRating?: never;
intendedPocketType?: never;
isBreakHeuristic?: never;
isDirect?: never;
isLeft?: never;
isLeftMiss?: never;
isRight?: never;
isRightMiss?: never;
isStraight?: never;
kickAngle?: never;
kickDistance?: never;
make?: never;
marginOfErrorInDegrees?: never;
missAngleInDegrees?: never;
notFilter?: never;
orFilters?: never;
playerIndex?: never;
runId: Array<Scalars["Int"]["input"]>; runId: Array<Scalars["Int"]["input"]>;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -1871,7 +1796,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength: FloatRangeFilter; runLength: FloatRangeFilter;
shotDirection?: never; shotDirection?: never;
@@ -1916,7 +1840,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection: Array<ShotDirectionEnum>; shotDirection: Array<ShotDirectionEnum>;
@@ -1961,7 +1884,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2006,7 +1928,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2051,7 +1972,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2096,7 +2016,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2141,7 +2060,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2186,7 +2104,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2231,7 +2148,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2276,7 +2192,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2321,7 +2236,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -2366,7 +2280,6 @@ export type FilterInput =
missAngleInDegrees?: never; missAngleInDegrees?: never;
notFilter?: never; notFilter?: never;
orFilters?: never; orFilters?: never;
playerIndex?: never;
runId?: never; runId?: never;
runLength?: never; runLength?: never;
shotDirection?: never; shotDirection?: never;
@@ -3969,6 +3882,7 @@ export type ShotProjectionGql = {
export type ShotTableStateGql = { export type ShotTableStateGql = {
__typename?: "ShotTableStateGQL"; __typename?: "ShotTableStateGQL";
frameImage?: Maybe<Scalars["String"]["output"]>;
frameIndex: Scalars["Int"]["output"]; frameIndex: Scalars["Int"]["output"];
shotId: Scalars["Int"]["output"]; shotId: Scalars["Int"]["output"];
tableState: TableStateGql; tableState: TableStateGql;
@@ -7822,6 +7736,7 @@ export type GetShotTableStateQuery = {
shotId: number; shotId: number;
videoId: number; videoId: number;
frameIndex: number; frameIndex: number;
frameImage?: string | null;
tableState: { tableState: {
__typename?: "TableStateGQL"; __typename?: "TableStateGQL";
identifierToPosition: Array<Array<number>>; identifierToPosition: Array<Array<number>>;
@@ -16602,6 +16517,7 @@ export const GetShotTableStateDocument = gql`
shotId shotId
videoId videoId
frameIndex frameIndex
frameImage
tableState { tableState {
...TableStateLayoutFields ...TableStateLayoutFields
} }

View File

@@ -44,9 +44,10 @@ fragment DetectionPocketPointFields on PocketPointsGQL {
# positions. Boxes, pocket boxes, and source points share the crop's # positions. Boxes, pocket boxes, and source points share the crop's
# coordinate space; the crop locates that space in the full frame. # coordinate space; the crop locates that space in the full frame.
# #
# Only the photo query selects this. The live and shot queries read a # Only the photo query selects this. The shot query returns its source
# frame out of a video the app never holds, so there is no image to # frame (frameImage) but positions there come from the stored homography,
# overlay, and getLiveTableState is polled every five seconds. # so there are no fresh boxes to draw; the live query returns no image at
# all and is polled every five seconds.
fragment TableStateDetectionFields on TableStateGQL { fragment TableStateDetectionFields on TableStateGQL {
identifierToBox { identifierToBox {
...DetectionBoxFields ...DetectionBoxFields
@@ -97,6 +98,9 @@ query GetShotTableState($shotId: Int!) {
shotId shotId
videoId videoId
frameIndex frameIndex
# The b64 JPEG frame the layout was detected in, so the client can show
# the shot's actual starting frame instead of the video's thumbnail.
frameImage
tableState { tableState {
...TableStateLayoutFields ...TableStateLayoutFields
} }

View File

@@ -250,11 +250,6 @@ input CreatePoolHallCameraInput {
claimTermsText: String = null claimTermsText: String = null
} }
input CreateShotShareLinkInput {
shotIds: [Int!]!
paddingSeconds: Float = null
}
input CreatePoolHallInput { input CreatePoolHallInput {
name: String! name: String!
address: String = null address: String = null
@@ -413,7 +408,6 @@ input FilterInput @oneOf {
userId: [Int!] userId: [Int!]
runId: [Int!] runId: [Int!]
username: [String!] username: [String!]
playerIndex: [Int!]
fargoRating: FloatRangeFilter fargoRating: FloatRangeFilter
make: [Boolean!] make: [Boolean!]
tags: [VideoTagInput!] tags: [VideoTagInput!]
@@ -1574,6 +1568,7 @@ type ShotTableStateGQL {
videoId: Int! videoId: Int!
frameIndex: Int! frameIndex: Int!
tableState: TableStateGQL! tableState: TableStateGQL!
frameImage: String
} }
input ShotsOrderingComponent @oneOf { input ShotsOrderingComponent @oneOf {
@@ -1967,11 +1962,6 @@ type UserSubscriptionStatusGQL {
stripeSubscriptionId: String stripeSubscriptionId: String
} }
type ShareLinkGQL {
slug: String!
url: String!
}
type VideoExportJobGQL { type VideoExportJobGQL {
id: Int! id: Int!
videoId: Int! videoId: Int!