Skip to content

Add MS Windows support#265

Open
sounakban wants to merge 11 commits into
NYUCCL:mainfrom
sounakban:main
Open

Add MS Windows support#265
sounakban wants to merge 11 commits into
NYUCCL:mainfrom
sounakban:main

Conversation

@sounakban
Copy link
Copy Markdown

Smile setup scripts are currently bash script, which can not be run on Windows. This PR will add OS agnostic node js scripts, that will work on all Operating Systems.

markkho and others added 11 commits April 9, 2026 11:54
- Add getrecruitment CLI (scripts/get_recruitment_data.mjs) for
getting bonusing data
- Fix key collision in Prolific URL params: rename SESSION_ID to
  prolific_session_id so it no longer overwrites smile's seedID
- Generate random test* IDs in Prolific demo URL for realistic testing
- Add googleapis dependency; wire get_secrets.sh to download script
- Fix race condition: await completeConsent() in router, show spinner
  in MainApp while Firebase doc is being created after consent
- Add trials prop to StroopExpView for parameterizable trial sets
- Add StroopInstructionsView with proper R/G/B key instructions
- Add DemographicSurveyMinimalView with age + gender only
- Add TaskFeedbackSurveyView without "upload data" button
- Replace Stroop quiz with task-specific comprehension questions
- Replace brain.svg with Necker cube + lab name in advertisement
- Clean up design.js: remove demo content, fix duplicate config,
  move demographics to after stroop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 11, 2026 00:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to make the project setup/deploy tooling work on Windows by replacing bash-based scripts with Node.js equivalents and wiring them into the build workflow. It also includes several template-level changes to experiment UX/components and recruitment/data handling.

Changes:

  • Replace sh scripts/*.sh commands with cross-platform node scripts/*.mjs tooling (setup, secrets, config upload, force deploy, git env generation) and invoke git env generation from vite.config.js.
  • Add UI/state to show a “Connecting...” screen while consent completion triggers DB connection, plus some store/router adjustments.
  • Update the default experiment template content (survey flow/components, quiz questions, Stroop views) and alter recruitment/data handling behavior.

Reviewed changes

Copilot reviewed 23 out of 25 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
vite.config.js Runs git env generation via Node before Vite config executes.
src/user/design.js Updates default experiment timeline/components (Stroop, surveys).
src/user/components/TaskFeedbackSurveyView.vue Adds a user-scoped feedback survey view.
src/user/components/stroop_exp/StroopInstructionsView.vue Adds custom Stroop instructions view.
src/user/components/stroop_exp/StroopExpView.vue Adds prop-driven trial list for Stroop experiment view.
src/user/components/quizQuestions.js Replaces example quiz questions with task-relevant ones.
src/core/utils/utils.js Changes Prolific session field naming in recruitment info.
src/core/stores/smilestore.js Adds dbConnecting, changes Prolific example URL, changes recruitment handling.
src/core/router.js Toggles dbConnecting around consent completion.
src/core/MainApp.vue Shows a full-screen “Connecting...” state when dbConnecting is true.
src/builtins/demographicSurvey/DemographicSurveyMinimalView.vue Adds a minimal demographic survey view.
src/builtins/advertisement/AdvertisementView.vue Replaces logo image with inline SVG “Lab logo” block.
scripts/update_config.mjs Cross-platform GitHub secret upload script.
scripts/setup_project.mjs Cross-platform setup script installing deps + git hooks.
scripts/get_secrets.sh Bash secrets fetch script (now alongside Node variant).
scripts/get_secrets.mjs Cross-platform secrets fetch script using gh api.
scripts/generate_git_env.mjs Cross-platform git-derived env file generator.
scripts/force_deploy.mjs Cross-platform workflow dispatch helper using gh workflow run.
scripts/download_data.mjs Changes export directory behavior and removes recruitment-info option.
README.md Replaces upstream README with fork/template setup instructions.
package.json Switches scripts to Node-based commands; adds getrecruitment command; adds deps.
package-lock.json Locks new dependencies and transitive updates.
.gitignore Ignores downloaded scripts/get_recruitment_data.mjs.
.github/workflows/docs-deploy.yml Converts docs workflow to a no-op for forks.
.github/workflows/deploy.yml Skips deploy when required secrets are not configured.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 594 to 608
setRecruitmentService(service, info) {
this.data.recruitmentService = service
this.private.recruitmentInfo = info
// recruitment IDs are not stored in Firebase — instead POST to a Google Form if configured
if (info && import.meta.env.VITE_RECRUITMENT_FORM_URL && import.meta.env.VITE_RECRUITMENT_FORM_ENTRY) {
const formData = new FormData()
formData.append(import.meta.env.VITE_RECRUITMENT_FORM_ENTRY, JSON.stringify({
session_id: this.browserPersisted.seedID,
service,
...info,
}))
fetch(import.meta.env.VITE_RECRUITMENT_FORM_URL, { method: 'POST', mode: 'no-cors', body: formData }).catch(
() => {}
) // fire and forget
}
},
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setRecruitmentService() no longer assigns this.private.recruitmentInfo = info. Several UI paths read smilestore.private.recruitmentInfo (e.g., StatusBar email prefill, ThanksView completion links), so this will leave those values undefined. Keep storing info in private.recruitmentInfo locally (even if you avoid writing it to Firebase) so downstream components still work.

Copilot uses AI. Check for mistakes.
Comment on lines +596 to +606
// recruitment IDs are not stored in Firebase — instead POST to a Google Form if configured
if (info && import.meta.env.VITE_RECRUITMENT_FORM_URL && import.meta.env.VITE_RECRUITMENT_FORM_ENTRY) {
const formData = new FormData()
formData.append(import.meta.env.VITE_RECRUITMENT_FORM_ENTRY, JSON.stringify({
session_id: this.browserPersisted.seedID,
service,
...info,
}))
fetch(import.meta.env.VITE_RECRUITMENT_FORM_URL, { method: 'POST', mode: 'no-cors', body: formData }).catch(
() => {}
) // fire and forget
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posting recruitment IDs directly from the client using VITE_RECRUITMENT_FORM_URL/ENTRY makes the endpoint and entry ID publicly visible in the shipped bundle, so anyone can submit arbitrary payloads to the form. If this data needs integrity, consider sending these IDs via a server-side endpoint/Cloud Function (or add some form of verification/rate limiting) rather than a public Google Form POST.

Copilot uses AI. Check for mistakes.
Comment thread src/core/router.js
Comment on lines +36 to +37
await api.completeConsent()
api.store.browserEphemeral.dbConnecting = false
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dbConnecting is set to true/false around await api.completeConsent(), but if completeConsent() throws/rejects, the flag will never be reset and the UI can get stuck on the “Connecting...” screen. Wrap the await in a try/finally (or ensure errors clear the flag) so dbConnecting is always restored.

Suggested change
await api.completeConsent()
api.store.browserEphemeral.dbConnecting = false
try {
await api.completeConsent()
} finally {
api.store.browserEphemeral.dbConnecting = false
}

Copilot uses AI. Check for mistakes.
Comment thread scripts/setup_project.mjs
Comment on lines +21 to +28
function installHook(hookName) {
const hooksDir = path.join(".git", "hooks");
const hookPath = path.join(hooksDir, hookName);
const hookContent = "#!/bin/sh\nnode scripts/generate_git_env.mjs\n";

fs.mkdirSync(hooksDir, { recursive: true });
fs.writeFileSync(hookPath, hookContent, "utf8");
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The installed git hook files are written but never marked executable. On macOS/Linux, non-executable hooks in .git/hooks/* won’t run, so generate_git_env won’t regenerate after commit/checkout. After writing the hook, set permissions (e.g., chmod 755) and consider using process.execPath in the hook body to avoid relying on node being on PATH.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +74
const hash = run("git", ["rev-parse", "--short", "HEAD"]).toLowerCase();
const branch = run("git", ["rev-parse", "--abbrev-ref", "HEAD"]).toLowerCase();
const lastMsg = run("git", ["log", "-1", "--pretty=%s"]);

const codenamizeOutput = run("node", ["scripts/codenamize.cjs", `/${owner}/${projectName}/${branch}`]);
const codeName = codenamizeOutput.split(/\r?\n/).pop() || "";

const envLines = [
"# DO NOT EDIT THIS FILE IT IS AUTOMATICALLY GENERATED",
"# this file is automatically generated by the post-commit hook (see scripts/generate_git_env).",
"",
`VITE_PROJECT_NAME = ${projectName}`,
`VITE_GIT_HASH = ${hash}`,
"VITE_GIT_REPO_NAME = ${VITE_PROJECT_NAME}",
`VITE_GIT_OWNER = ${owner}`,
`VITE_GIT_BRANCH_NAME = ${branch}`,
'VITE_DEPLOY_BASE_PATH = "/${VITE_GIT_OWNER}/${VITE_GIT_REPO_NAME}/${VITE_GIT_BRANCH_NAME}/"',
`VITE_CODE_NAME = ${codeName}`,
`VITE_GIT_LAST_MSG = ${lastMsg}`,
"",
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lastMsg (commit subject) is written into .env.git.local without escaping/quoting. Commit subjects containing #, newlines, or quotes can break dotenv parsing (and therefore Vite env loading). Escape/quote this value (e.g., write it as a JSON-string-escaped value, or wrap in quotes and escape "/newlines/#).

Copilot uses AI. Check for mistakes.
dbChanges: true,
urls: {
prolific: '?PROLIFIC_PID=XXXX&STUDY_ID=XXXX&SESSION_ID=XXXXX#/welcome/prolific/',
prolific: `?PROLIFIC_PID=test${Array.from({length: 6}, () => Math.floor(Math.random() * 16).toString(16)).join('')}&STUDY_ID=test${Array.from({length: 6}, () => Math.floor(Math.random() * 16).toString(16)).join('')}&SESSION_ID=test${Array.from({length: 6}, () => Math.floor(Math.random() * 16).toString(16)).join('')}#/welcome/prolific/`,
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initBrowserEphemeral.urls.prolific uses Math.random() during module initialization, which makes the initial store state non-deterministic (and can complicate debugging/test snapshots). If the goal is just an example URL, consider using a stable placeholder (e.g., XXXX) or generate the random example at the moment it’s displayed instead of at import time.

Suggested change
prolific: `?PROLIFIC_PID=test${Array.from({length: 6}, () => Math.floor(Math.random() * 16).toString(16)).join('')}&STUDY_ID=test${Array.from({length: 6}, () => Math.floor(Math.random() * 16).toString(16)).join('')}&SESSION_ID=test${Array.from({length: 6}, () => Math.floor(Math.random() * 16).toString(16)).join('')}#/welcome/prolific/`,
prolific:
'?PROLIFIC_PID=testXXXXXX&STUDY_ID=testXXXXXX&SESSION_ID=testXXXXXX#/welcome/prolific/',

Copilot uses AI. Check for mistakes.
Comment on lines 9 to 40
@@ -18,19 +35,7 @@ const trials = api.steps.append([
},
])

trials[0]
.append([
{ id: 'a', word: 'SHIP', color: 'red', condition: 'unrelated' },
{ id: 'b', word: 'MONKEY', color: 'green', condition: 'unrelated' },
{ id: 'c', word: 'ZAMBONI', color: 'blue', condition: 'unrelated' },
{ id: 'd', word: 'RED', color: 'red', condition: 'congruent' },
{ id: 'e', word: 'GREEN', color: 'green', condition: 'congruent' },
{ id: 'f', word: 'BLUE', color: 'blue', condition: 'congruent' },
{ id: 'g', word: 'GREEN', color: 'red', condition: 'incongruent' },
{ id: 'h', word: 'BLUE', color: 'green', condition: 'incongruent' },
{ id: 'i', word: 'RED', color: 'blue', condition: 'incongruent' },
])
.shuffle()
trials[0].append(props.trials).shuffle()

trials.append([{ id: 'summary' }])
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file defines both a trials prop (props.trials) and a local const trials = api.steps.append(...), which makes the code hard to read and easy to misuse. Rename one of them (e.g., trialSpec for the stepper spec, or trialList for the prop) to avoid the name collision.

Copilot uses AI. Check for mistakes.
Comment thread src/user/design.js
Comment on lines 18 to 36
// 1. Import main built-in View components
import AdvertisementView from '@/builtins/advertisement/AdvertisementView.vue'
import MTurkRecruitView from '@/builtins/mturk/MTurkRecruitView.vue'
import InformedConsentView from '@/builtins/informedConsent/InformedConsentView.vue'
import DemographicSurveyView from '@/builtins/demographicSurvey/DemographicSurveyView.vue'
import DeviceSurveyView from '@/builtins/deviceSurvey/DeviceSurveyView.vue'
import InstructionsView from '@/builtins/instructions/InstructionsView.vue'
import DemographicSurveyView from '@/builtins/demographicSurvey/DemographicSurveyMinimalView.vue'
import InstructionsQuizView from '@/builtins/instructionsQuiz/InstructionsQuiz.vue'
import DebriefView from '@/builtins/debrief/DebriefView.vue'
import TaskFeedbackSurveyView from '@/builtins/taskFeedbackSurvey/TaskFeedbackSurveyView.vue'
import ThanksView from '@/builtins/thanks/ThanksView.vue'
import WithdrawView from '@/builtins/withdraw/WithdrawView.vue'
import WindowSizerView from '@/builtins/windowSizer/WindowSizerView.vue'

// 2. Import user View components
import ExpView from '@/builtins/demoTasks/ExpView.vue'
import FavoriteNumber from '@/builtins/demoTasks/FavoriteNumber.vue'
import FavoriteColor from '@/builtins/demoTasks/FavoriteColor.vue'
import InstructionsView from '@/user/components/stroop_exp/StroopInstructionsView.vue'
import StroopExpView from '@/user/components/stroop_exp/StroopExpView.vue'
import TaskFeedbackSurveyView from '@/user/components/TaskFeedbackSurveyView.vue'

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is described as adding Windows support for setup scripts, but it also changes experiment flow/components (custom Stroop instructions/experiment, minimal demographic survey, new feedback survey) and recruitment handling. Consider splitting these functional changes into separate PRs or updating the PR description to reflect the additional scope so reviewers can assess impact appropriately.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants