diff --git a/config/2026/config.json b/config/2026/config.json
index 9b0ef6306..914b74ec7 100644
--- a/config/2026/config.json
+++ b/config/2026/config.json
@@ -74,6 +74,14 @@
"formResetBehavior": "preserve",
"defaultValue": ""
},
+ {
+ "title": "Assigned Station",
+ "description": "The station assigned to the scout (e.g., R1, R2, R3, B1, B2, B3). Use this to automatically assign the team and robot position based on the match number and station.",
+ "type": "TBA-assigned-station",
+ "required": false,
+ "code": "assignedStation",
+ "formResetBehavior": "preserve"
+ },
{
"title": "Match Number",
"description": "Select the match number.",
@@ -90,7 +98,8 @@
"required": true,
"code": "robot",
"formResetBehavior": "preserve",
- "defaultValue": null
+ "defaultValue": null,
+ "autoAssignFromFieldCode": "assignedStation"
},
{
"title": "Starting Position",
diff --git a/package.json b/package.json
index 20fb5a02b..8f4e31b2f 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"dev": "vite",
"build": "tsc && vite build ",
"preview": "vite preview",
- "schema": "ts-node src/scripts/generateJsonSchema.ts src/assets/schema.json && cp src/assets/schema.json public/schema.json"
+ "schema": "npx tsx src/scripts/generateJsonSchema.ts src/assets/schema.json && cp src/assets/schema.json public/schema.json"
},
"dependencies": {
"@headlessui/react": "^1.7.19",
diff --git a/public/schema.json b/public/schema.json
index fa58f2281..84cc3413a 100644
--- a/public/schema.json
+++ b/public/schema.json
@@ -11,7 +11,7 @@
},
"year": {
"type": "number",
- "description": "The year this scouting config is relevant for."
+ "description": "The year this scouting config is relevant for. Defaults to the current year if not provided."
},
"delimiter": {
"type": "string",
@@ -294,6 +294,45 @@
],
"additionalProperties": false
},
+ {
+ "type": "object",
+ "properties": {
+ "title": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title"
+ },
+ "description": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description"
+ },
+ "type": {
+ "type": "string",
+ "const": "multi-counter"
+ },
+ "required": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required"
+ },
+ "code": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code"
+ },
+ "disabled": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled"
+ },
+ "formResetBehavior": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior"
+ },
+ "defaultValue": {
+ "type": "number",
+ "default": 0,
+ "description": "The default value"
+ }
+ },
+ "required": [
+ "title",
+ "type",
+ "required",
+ "code"
+ ],
+ "additionalProperties": false
+ },
{
"type": "object",
"properties": {
@@ -741,6 +780,10 @@
"timerDuration": {
"type": "number",
"description": "Expected duration in seconds (for UI reference, e.g., 15 for auto, 135 for teleop)"
+ },
+ "autoStopSeconds": {
+ "type": "number",
+ "description": "Automatically stop the timer after this many seconds. Useful to prevent the timer from running past the match phase duration."
}
},
"required": [
@@ -786,7 +829,16 @@
"type": "number"
},
"robotPosition": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "R1",
+ "R2",
+ "R3",
+ "B1",
+ "B2",
+ "B3"
+ ],
+ "description": "The robot position in the alliance. R = Red alliance, B = Blue alliance, 1/2/3 = position on the alliance."
}
},
"required": [
@@ -801,6 +853,10 @@
],
"default": null,
"description": "The default team and robot position"
+ },
+ "autoAssignFromFieldCode": {
+ "type": "string",
+ "description": "Optional code of another field to auto-assign"
}
},
"required": [
@@ -857,6 +913,59 @@
"code"
],
"additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "title": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title"
+ },
+ "description": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description"
+ },
+ "type": {
+ "type": "string",
+ "const": "TBA-assigned-station"
+ },
+ "required": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required"
+ },
+ "code": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code"
+ },
+ "disabled": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled"
+ },
+ "formResetBehavior": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior"
+ },
+ "defaultValue": {
+ "type": "string",
+ "default": "",
+ "description": "If assigning by driver station, the driver station to use for selecting team and robot position across matches"
+ },
+ "choices": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "default": {
+ "R1": "Red 1",
+ "R2": "Red 2",
+ "R3": "Red 3",
+ "B1": "Blue 1",
+ "B2": "Blue 2",
+ "B3": "Blue 3"
+ }
+ }
+ },
+ "required": [
+ "title",
+ "type",
+ "required",
+ "code"
+ ],
+ "additionalProperties": false
}
]
}
@@ -876,7 +985,6 @@
"required": [
"title",
"page_title",
- "year",
"delimiter",
"teamNumber",
"sections"
diff --git a/src/assets/schema.json b/src/assets/schema.json
index fa58f2281..84cc3413a 100644
--- a/src/assets/schema.json
+++ b/src/assets/schema.json
@@ -11,7 +11,7 @@
},
"year": {
"type": "number",
- "description": "The year this scouting config is relevant for."
+ "description": "The year this scouting config is relevant for. Defaults to the current year if not provided."
},
"delimiter": {
"type": "string",
@@ -294,6 +294,45 @@
],
"additionalProperties": false
},
+ {
+ "type": "object",
+ "properties": {
+ "title": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title"
+ },
+ "description": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description"
+ },
+ "type": {
+ "type": "string",
+ "const": "multi-counter"
+ },
+ "required": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required"
+ },
+ "code": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code"
+ },
+ "disabled": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled"
+ },
+ "formResetBehavior": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior"
+ },
+ "defaultValue": {
+ "type": "number",
+ "default": 0,
+ "description": "The default value"
+ }
+ },
+ "required": [
+ "title",
+ "type",
+ "required",
+ "code"
+ ],
+ "additionalProperties": false
+ },
{
"type": "object",
"properties": {
@@ -741,6 +780,10 @@
"timerDuration": {
"type": "number",
"description": "Expected duration in seconds (for UI reference, e.g., 15 for auto, 135 for teleop)"
+ },
+ "autoStopSeconds": {
+ "type": "number",
+ "description": "Automatically stop the timer after this many seconds. Useful to prevent the timer from running past the match phase duration."
}
},
"required": [
@@ -786,7 +829,16 @@
"type": "number"
},
"robotPosition": {
- "type": "string"
+ "type": "string",
+ "enum": [
+ "R1",
+ "R2",
+ "R3",
+ "B1",
+ "B2",
+ "B3"
+ ],
+ "description": "The robot position in the alliance. R = Red alliance, B = Blue alliance, 1/2/3 = position on the alliance."
}
},
"required": [
@@ -801,6 +853,10 @@
],
"default": null,
"description": "The default team and robot position"
+ },
+ "autoAssignFromFieldCode": {
+ "type": "string",
+ "description": "Optional code of another field to auto-assign"
}
},
"required": [
@@ -857,6 +913,59 @@
"code"
],
"additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "title": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/title"
+ },
+ "description": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/description"
+ },
+ "type": {
+ "type": "string",
+ "const": "TBA-assigned-station"
+ },
+ "required": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/required"
+ },
+ "code": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/code"
+ },
+ "disabled": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/disabled"
+ },
+ "formResetBehavior": {
+ "$ref": "#/properties/sections/items/properties/fields/items/anyOf/0/properties/formResetBehavior"
+ },
+ "defaultValue": {
+ "type": "string",
+ "default": "",
+ "description": "If assigning by driver station, the driver station to use for selecting team and robot position across matches"
+ },
+ "choices": {
+ "type": "object",
+ "additionalProperties": {
+ "type": "string"
+ },
+ "default": {
+ "R1": "Red 1",
+ "R2": "Red 2",
+ "R3": "Red 3",
+ "B1": "Blue 1",
+ "B2": "Blue 2",
+ "B3": "Blue 3"
+ }
+ }
+ },
+ "required": [
+ "title",
+ "type",
+ "required",
+ "code"
+ ],
+ "additionalProperties": false
}
]
}
@@ -876,7 +985,6 @@
"required": [
"title",
"page_title",
- "year",
"delimiter",
"teamNumber",
"sections"
diff --git a/src/components/inputs/BaseInputProps.ts b/src/components/inputs/BaseInputProps.ts
index f5a33d5fe..32f8dfa99 100644
--- a/src/components/inputs/BaseInputProps.ts
+++ b/src/components/inputs/BaseInputProps.ts
@@ -15,6 +15,7 @@ export const inputTypeSchema = z
'action-tracker',
'TBA-team-and-robot',
'TBA-match-number',
+ 'TBA-assigned-station',
])
.describe('The type of input');
@@ -150,16 +151,35 @@ export const actionTrackerInputSchema = inputBaseSchema.extend({
),
});
+export const robotPosition = z
+ .enum(['R1', 'R2', 'R3', 'B1', 'B2', 'B3'])
+ .describe(
+ 'The robot position in the alliance. R = Red alliance, B = Blue alliance, 1/2/3 = position on the alliance.',
+ );
+
+export const robotPositionLabels = {
+ R1: 'Red 1',
+ R2: 'Red 2',
+ R3: 'Red 3',
+ B1: 'Blue 1',
+ B2: 'Blue 2',
+ B3: 'Blue 3',
+};
+
export const tbaTeamAndRobotInputSchema = inputBaseSchema.extend({
type: z.literal('TBA-team-and-robot'),
defaultValue: z
.object({
teamNumber: z.number(),
- robotPosition: z.string(),
+ robotPosition,
})
.nullable()
.default(null)
.describe('The default team and robot position'),
+ autoAssignFromFieldCode: z
+ .string()
+ .optional()
+ .describe('Optional code of another field to auto-assign'),
});
export const tbaMatchNumberInputSchema = inputBaseSchema.extend({
@@ -169,6 +189,17 @@ export const tbaMatchNumberInputSchema = inputBaseSchema.extend({
defaultValue: z.number().default(0).describe('The default value'),
});
+export const tbaAssignedStationInputSchema = selectInputSchema.extend({
+ type: z.literal('TBA-assigned-station'),
+ choices: z.record(z.string()).default(robotPositionLabels),
+ defaultValue: z
+ .string()
+ .default('')
+ .describe(
+ 'If assigning by driver station, the driver station to use for selecting team and robot position across matches',
+ ),
+});
+
export const sectionSchema = z.object({
name: z.string(),
fields: z.array(
@@ -186,6 +217,7 @@ export const sectionSchema = z.object({
actionTrackerInputSchema,
tbaTeamAndRobotInputSchema,
tbaMatchNumberInputSchema,
+ tbaAssignedStationInputSchema,
]),
),
});
diff --git a/src/components/inputs/ConfigurableInput.tsx b/src/components/inputs/ConfigurableInput.tsx
index 9b4d4a07d..f5dad2656 100644
--- a/src/components/inputs/ConfigurableInput.tsx
+++ b/src/components/inputs/ConfigurableInput.tsx
@@ -46,5 +46,7 @@ export default function ConfigurableInput(props: ConfigurableInputProps) {
return