From 599d525dbcddccca70a2c0d3cead30529c2ebdba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:53:36 +0000 Subject: [PATCH 1/2] Initial plan From 7c228bd7ec2fd3f733b4e682c2a4049423999775 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:01:04 +0000 Subject: [PATCH 2/2] Add effortCheck config parameter to control attention check Co-authored-by: JamesPHoughton <4304478+JamesPHoughton@users.noreply.github.com> --- client/src/App.jsx | 10 ++++++- client/src/intro-exit/AttentionCheck.jsx | 12 ++++++-- docs/batchConfig.md | 30 +++++++++++++++++++ .../src/preFlight/validateBatchConfig.test.js | 24 +++++++++++++++ server/src/preFlight/validateBatchConfig.ts | 3 ++ 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index d93d3e14..6bfe0bd4 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -57,7 +57,15 @@ function InnerParticipant() { const introSequence = globals.get("recruitingBatchIntroSequence"); function introSteps() { - const steps = [Consent, AttentionCheck, EquipmentCheck, EnterNickname]; + const steps = [Consent]; + + // Add AttentionCheck based on effortCheck config + const effortCheck = batchConfig?.effortCheck ?? true; // default to true if not specified + if (effortCheck !== false) { + steps.push(AttentionCheck); + } + + steps.push(EquipmentCheck, EnterNickname); if (introSequence?.introSteps) { introSequence.introSteps.forEach((step, index) => { diff --git a/client/src/intro-exit/AttentionCheck.jsx b/client/src/intro-exit/AttentionCheck.jsx index 5240ccce..4a4447fb 100644 --- a/client/src/intro-exit/AttentionCheck.jsx +++ b/client/src/intro-exit/AttentionCheck.jsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; import { usePlayer } from "@empirica/core/player/classic/react"; +import { useGlobal } from "@empirica/core/player/react"; import { Button } from "../components/Button"; export function AttentionCheck({ next }) { @@ -7,8 +8,15 @@ export function AttentionCheck({ next }) { const [sentenceInput, setSentenceInput] = useState(""); const [loadedTime, setLoadedTime] = useState(-1); const player = usePlayer(); - const originalString = - "I agree to participate in this study to the best of my ability."; + const globals = useGlobal(); + const batchConfig = globals?.get("recruitingBatchConfig"); + + // Get effortCheck config, default to true with default text + const effortCheck = batchConfig?.effortCheck ?? true; + const originalString = + typeof effortCheck === "string" + ? effortCheck + : "I agree to participate in this study to the best of my ability."; useEffect(() => { console.log("Intro: Attention Check"); diff --git a/docs/batchConfig.md b/docs/batchConfig.md index e13df3bb..4f7ad53c 100644 --- a/docs/batchConfig.md +++ b/docs/batchConfig.md @@ -107,6 +107,36 @@ Set to false if you are not using webcams in your experiment, and you don't care Must be a boolean. If you do not wish to check participant audio, enter `"checkAudio": false`. Set this to false if you aren't using webcams OR microphones. Has no effect unless `checkVideo` is also set to false. +### `effortCheck` + +Controls whether to show the sentence typing effort check during the intro steps. This is an optional parameter with three possible configurations: + +- `"effortCheck": true` (default if not specified) - Shows the effort check with the default sentence: "I agree to participate in this study to the best of my ability." +- `"effortCheck": false` - Skips the effort check entirely +- `"effortCheck": "Your custom sentence here"` - Shows the effort check with a custom sentence that participants must type exactly + +Example configurations: + +```json +{ + "effortCheck": false +} +``` + +```json +{ + "effortCheck": true +} +``` + +```json +{ + "effortCheck": "The quick brown fox jumps over the lazy dog" +} +``` + +This feature allows experiment designers to prioritize speed in the intro stages by skipping the effort check, or to customize the sentence participants must type. + ### `introSequence` The name of the sequence defined in `treatmentFile` to be shown to all participants prior to assignment to treatment condition diff --git a/server/src/preFlight/validateBatchConfig.test.js b/server/src/preFlight/validateBatchConfig.test.js index a1362dee..78540885 100644 --- a/server/src/preFlight/validateBatchConfig.test.js +++ b/server/src/preFlight/validateBatchConfig.test.js @@ -250,3 +250,27 @@ test.skip("treatment name is not present in treatment file", () => { expect(result.success).toBe(false); // Todo: add check for error message }); + +test("effortCheck boolean true", () => { + const config = JSON.parse(JSON.stringify(passingConfig)); + config.effortCheck = true; + expect(() => validateBatchConfig(config)).not.to.throw(ValidationError); +}); + +test("effortCheck boolean false", () => { + const config = JSON.parse(JSON.stringify(passingConfig)); + config.effortCheck = false; + expect(() => validateBatchConfig(config)).not.to.throw(ValidationError); +}); + +test("effortCheck custom string", () => { + const config = JSON.parse(JSON.stringify(passingConfig)); + config.effortCheck = "The quick brown fox jumps over the lazy dog"; + expect(() => validateBatchConfig(config)).not.to.throw(ValidationError); +}); + +test("effortCheck undefined/not provided", () => { + const config = JSON.parse(JSON.stringify(passingConfig)); + // effortCheck is not set, should be optional and work fine + expect(() => validateBatchConfig(config)).not.to.throw(ValidationError); +}); diff --git a/server/src/preFlight/validateBatchConfig.ts b/server/src/preFlight/validateBatchConfig.ts index 8d68055d..9763c59c 100644 --- a/server/src/preFlight/validateBatchConfig.ts +++ b/server/src/preFlight/validateBatchConfig.ts @@ -183,6 +183,9 @@ export const batchConfigSchema = z checkVideo: z.boolean({ message: `Must be a boolean. If you do not wish to check participant video, enter "false"`, }), + effortCheck: z + .union([z.boolean(), z.string()]) + .optional(), }) .strict() .superRefine((obj, ctx) => {