Skip to content

Custom Experiments: Add sounds and fix minor TODOs - #244

Merged
jdpigeon merged 9 commits into
mainfrom
sound-stimuli-and-todos
Aug 19, 2026
Merged

Custom Experiments: Add sounds and fix minor TODOs#244
jdpigeon merged 9 commits into
mainfrom
sound-stimuli-and-todos

Conversation

@jdpigeon

@jdpigeon jdpigeon commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds optional sound stimuli to custom experiments and closes out the top three TODOS.md items (restore / accept / allow), plus fixes found during QA.

Sound stimuli

  • Each condition gets an optional Sound Folder (.mp3/.wav/.m4a/.ogg) next to its image folder. Sounds play at trial onset — the same moment the EEG marker fires.
  • Co-assignment defaults: visual-only unchanged; auditory-only = one trial per sound (speaker glyph on screen); visual + auditory = one trial per image with sounds paired index-wise alphabetically, cycling when fewer sounds than images. Pairs shuffle as units. Directory listings are sorted so pairing is deterministic cross-platform.
  • Custom experiments now have their own lab.js template (experiments/custom/experiment.ts) instead of borrowing Faces/Houses; renders <img> and/or <audio autoplay> per trial.
  • Plumbing: fs:readAudioFiles IPC, FILE_TYPES.AUDIO_DIR dialog on the same StimulusFileAccess allowlist, media-src bwfile: CSP, media.audio preload, Sound column in Trials.

TODO items closed

  • Restore custom experiments (code side) — custom-specific template, 2020 leftovers deleted (custom/experiment.js, protocols/custom.ts). Human click-through + Muse ERP remain (re-scoped in TODOS.md).
  • Accept .webp — Conditions helper copy now lists all accepted extensions (extname check landed in Restore Custom Experiments #241).
  • Allow pasterole: 'editMenu' on macOS + default menus; right-click cut/copy/paste on editable fields in prod.

QA fixes (from live testing)

  • bwfile scheme registered without supportFetchAPI broke every custom study at prepare (lab.js preloads audio via fetch(); images dodged it via Image()).
  • Sound count vanished after navigating away (state lost on remount + shouldComponentUpdate ignored state).
  • Deleted workspaces lingered on Home: unawaited deleteWorkspaceDir raced readWorkspaces().

Guardrail

  • @typescript-eslint/no-floating-promises (warn, type-aware, src-scoped) — catches the unawaited-async bug class above. ~25 pre-existing hits left untouched (out of scope).

Test plan

  • 60/60 vitest (7 new: pairing/cycling, audio-only trials, audiopath URLs, sound-count remount)
  • typecheck + lint clean on touched files
  • Manual QA in Electron: sounds play in preview, counts persist, workspace delete immediate
  • Muse ERP with auditory condition (follow-up)

🤖 Generated with Claude Code

https://claude.ai/code/session_01ERuqciorhJChN9btAczwj5

jdpigeon and others added 4 commits August 18, 2026 16:29
macOS had no Edit menu at all, so Cmd+V/C/X/Z were dead in every input.
Adds role:'editMenu' to the Darwin and default templates, and an
isEditable-gated context menu (cut/copy/paste/selectAll) in prod; the dev
context menu gets the same items above Inspect element.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERuqciorhJChN9btAczwj5
fs:readImages has accepted .gif/.webp since the extname fix; the helper
text still claimed png/jpg/jpeg only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERuqciorhJChN9btAczwj5
Each custom condition can now carry a folder of sounds (.mp3/.wav/.m4a/.ogg)
alongside or instead of its image folder. Sounds play at trial onset — the
same moment the EEG marker fires — via an <audio autoplay> element in a new
custom-specific lab.js template (custom no longer borrows the Faces/Houses
template; audio-only trials show a speaker glyph).

Co-assignment defaults: one trial per image with sounds paired index-wise in
alphabetical order (cycling when fewer sounds than images); audio-only
conditions get one trial per sound; pairs shuffle as units. fs:readImages /
fs:readAudioFiles now sort directory listings so pairing is deterministic
across platforms.

Plumbing: fs:readAudioFiles IPC channel (main/preload/d.ts/storage),
FILE_TYPES.AUDIO_DIR dialog reusing the StimulusFileAccess allowlist, CSP
media-src bwfile:, media.audio preload in ExperimentWindow, sound-folder
picker in Conditions and a Sound column in Trials.

Also removes the unused 2020 leftovers experiments/custom/experiment.js and
utils/labjs/protocols/custom.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERuqciorhJChN9btAczwj5
@jdpigeon jdpigeon changed the title Custom Experiments: Add Sound Stimuli and fix minor TODOs Custom Experiments: Add sounds and fix minor TODOs Aug 19, 2026
jdpigeon and others added 5 commits August 19, 2026 13:40
- lab.js preloads options.media.audio via fetch(), which requires
  supportFetchAPI on the privileged scheme — without it every custom study
  failed to prepare with 'URL scheme "bwfile" is not supported' (images
  survived because Image() loading doesn't go through fetch). Registered
  bwfile with supportFetchAPI + corsEnabled, matching pyodide:.
- StimuliDesignColumn kept the sound count only in component state, so it
  vanished on remount; it now re-reads the folder on mount/audioDir change.
  Also let shouldComponentUpdate see state changes — it previously blocked
  re-render on any setState.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERuqciorhJChN9btAczwj5
deleteWorkspaceDir was fired without await, so readWorkspaces() listed the
directory before shell.trashItem finished — the deleted workspace lingered
on the homepage and a second delete errored on the already-trashed dir.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERuqciorhJChN9btAczwj5
Type-aware linting via projectService, scoped to src TS files (internals/
isn't in tsconfig.json). Catches the fire-and-forget async class that left
deleted workspaces on the Home screen. warn severity for gradual adoption
(~25 pre-existing hits, untouched); new intentional fire-and-forget calls
are marked with void.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ERuqciorhJChN9btAczwj5
Moves the image-resolution tip into a small box with a working
imageresizer.com link, and drops the sound/image pairing explanation
from the Conditions instructions per review feedback. Adds a
setWindowOpenHandler so target="_blank" links actually open (Electron
denies window.open by default). Keeps the filePaths[0] index access
in loadDialog, silencing prefer-destructuring instead of rewriting it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YV9RAfwYDcTsLRyyCtZBpN
@jdpigeon
jdpigeon merged commit b7b48b6 into main Aug 19, 2026
10 checks passed
@jdpigeon
jdpigeon deleted the sound-stimuli-and-todos branch August 19, 2026 18:41
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.

1 participant