diff --git a/src/core/BlockUnit.ts b/src/core/BlockUnit.ts index 7b9df93..7985e09 100644 --- a/src/core/BlockUnit.ts +++ b/src/core/BlockUnit.ts @@ -10,6 +10,11 @@ function makeSeededRandom(seed: number): () => number { }; } +/** + * Block-level trial controller (browser counterpart of Python's BlockUnit). + * + * Generates a shuffled, weighted condition sequence for a block of trials. + */ export class BlockUnit { block_id: string; block_idx: number; @@ -37,6 +42,12 @@ export class BlockUnit { this.seed = Number(seeds[block_idx] ?? settings.overall_seed ?? 2025); } + /** + * Generate a shuffled condition sequence for this block. + * + * Uses weighted sampling to distribute remainder trials, then Fisher-Yates shuffle. + * Pass `func` to delegate generation to a custom function instead. + */ generate_conditions(options: { weights?: number[] | null; func?: (...args: any[]) => string[]; diff --git a/src/core/StimBank.ts b/src/core/StimBank.ts index 60371a4..ca48880 100644 --- a/src/core/StimBank.ts +++ b/src/core/StimBank.ts @@ -16,6 +16,12 @@ function formatTemplate(template: string, vars: Record): string }); } +/** + * Immutable stimulus registry (browser counterpart of Python's StimBank). + * + * Holds {@link StimSpec} definitions keyed by name. Returns {@link StimRef} + * handles from {@link get} that are resolved to concrete specs at runtime. + */ export class StimBank { private readonly config: Record; @@ -23,6 +29,7 @@ export class StimBank { this.config = config; } + /** Return a {@link StimRef} handle for a named stimulus. Throws if not found. */ get(key: string): StimRef { if (!this.config[key]) { throw new Error(`Stimulus '${key}' not found.`); @@ -30,6 +37,7 @@ export class StimBank { return { kind: "stim_ref", key }; } + /** Resolve a ref or key to a deep-cloned {@link StimSpec}. Throws if not found. */ resolve(ref: StimRef | string): StimSpec { const key = typeof ref === "string" ? ref : ref.key; const spec = this.config[key]; @@ -59,6 +67,10 @@ export class StimBank { return this.format(name, vars); } + /** + * Register `_voice` entries for the given text stimuli. + * Uses pre-recorded audio assets when available, falls back to Web Speech API. + */ convert_to_voice( keys: string[] | string, options: { @@ -140,6 +152,10 @@ export class StimBank { } } +/** + * Resolve a potentially late-bound stimulus input to a concrete {@link StimSpec}. + * Handles {@link Resolver} functions, {@link StateRef} lookups, string keys, and {@link StimRef} handles. + */ export function resolveStimInput( input: Resolvable, snapshot: TrialSnapshot, diff --git a/src/core/StimUnit.ts b/src/core/StimUnit.ts index 44e71b5..5924d9c 100644 --- a/src/core/StimUnit.ts +++ b/src/core/StimUnit.ts @@ -11,6 +11,12 @@ import { TrialBuilder } from "./TrialBuilder"; type StageStatePatch = Record>; +/** + * Builder for a single trial stage (browser counterpart of Python's StimUnit). + * + * Chain `.addStim()` → `.show()` / `.captureResponse()` / `.waitAndContinue()` → + * optionally `.set_state()` / `.to_dict()` → then call `.compile()` to produce a {@link CompiledStage}. + */ export class StimUnit { readonly label: string; private readonly trial: TrialBuilder; @@ -125,6 +131,7 @@ export class StimUnit { return this; } + /** Create a {@link StateRef} pointing to a key in this unit's runtime state. */ ref(key: string): StateRef { return { kind: "state_ref", diff --git a/src/core/SubInfo.ts b/src/core/SubInfo.ts index f8a1cf5..9127eb1 100644 --- a/src/core/SubInfo.ts +++ b/src/core/SubInfo.ts @@ -11,6 +11,12 @@ function coerceFieldValue(field: SubInfoField, raw: string): string | number { return raw; } +/** + * Participant information form (browser counterpart of Python's SubInfo). + * + * Renders an HTML form from field definitions and returns validated responses + * as a Promise resolved on submit. + */ export class SubInfo { private readonly fields: SubInfoField[]; private readonly mapping: Record; @@ -25,6 +31,7 @@ export class SubInfo { this.mapping = config.subinfo_mapping; } + /** Render the participant form into `container` and resolve with validated responses on submit. */ collect(container: HTMLElement): Promise> { container.innerHTML = ""; const wrapper = document.createElement("div"); diff --git a/src/core/TaskSettings.ts b/src/core/TaskSettings.ts index f6a87af..4506b91 100644 --- a/src/core/TaskSettings.ts +++ b/src/core/TaskSettings.ts @@ -18,6 +18,12 @@ function makeSeededRandom(seed: number): () => number { }; } +/** + * Experiment configuration container (browser counterpart of Python's TaskSettings). + * + * Create via {@link TaskSettings.from_dict} with a parsed YAML config object. + * Holds window display, block/trial structure, seeding, and condition weights. + */ export class TaskSettings { [key: string]: unknown; @@ -38,6 +44,10 @@ export class TaskSettings { trial_per_block?: number; subject_id?: string; + /** + * Create a TaskSettings from a flat config dictionary. + * Validates `trial_per_block` consistency and initialises block seeds. + */ static from_dict(config: SettingsLike): TaskSettings { const settings = new TaskSettings(); Object.assign(settings, config); @@ -71,6 +81,7 @@ export class TaskSettings { .map(() => Math.floor(rng() * 100000)); } + /** Merge participant info into settings and derive per-subject seeds when `seed_mode` is `"same_within_sub"`. */ add_subinfo(subinfo: Record): void { Object.assign(this, subinfo); const subjectId = String(subinfo.subject_id ?? ""); @@ -88,6 +99,7 @@ export class TaskSettings { } } + /** Return validated weight vector aligned to `conditions`, or `null` for equal weighting. */ resolve_condition_weights(): number[] | null { const raw = this.condition_weights; if (raw == null) { diff --git a/src/core/TrialBuilder.ts b/src/core/TrialBuilder.ts index 910a04e..4721db1 100644 --- a/src/core/TrialBuilder.ts +++ b/src/core/TrialBuilder.ts @@ -6,6 +6,11 @@ import type { } from "./types"; import { StimUnit } from "./StimUnit"; +/** + * Builds a single {@link CompiledTrial} from an ordered sequence of {@link StimUnit} stages. + * + * Typical usage: `new TrialBuilder(meta).unit("fixation").addStim(...).show()...build()` + */ export class TrialBuilder { readonly trial_id: number | string; readonly block_id: string | null; @@ -27,6 +32,7 @@ export class TrialBuilder { this.condition = meta.condition; } + /** Create and register a new {@link StimUnit} stage with the given label. */ unit(label: string): StimUnit { const unit = new StimUnit(label, this); this.units.push(unit); @@ -42,6 +48,7 @@ export class TrialBuilder { this.trialState[key] = value; } + /** Compile all registered units into a {@link CompiledTrial}. */ build(): CompiledTrial { return { trial_id: this.trial_id, diff --git a/src/core/reducer.ts b/src/core/reducer.ts index 27e468c..173b6a0 100644 --- a/src/core/reducer.ts +++ b/src/core/reducer.ts @@ -38,6 +38,10 @@ const RAW_FIELDS = new Set([ "task_factors" ]); +/** + * Resolve a {@link Resolvable} value: call it if it's a {@link Resolver}, + * look up state if it's a {@link StateRef}, or return it as-is. + */ export function resolveValue( value: Resolvable, snapshot: TrialSnapshot, @@ -63,6 +67,12 @@ export function splitRawExtraData(unitState: Record): Record { kind: "state_ref"; unit_label: string; @@ -12,6 +29,7 @@ export interface StateRef { __type?: T; } +/** Read-only snapshot of a trial's accumulated state, passed to Resolvers and finalizers. */ export interface TrialSnapshot { trial_id: number | string; block_id: string | null; @@ -21,12 +39,18 @@ export interface TrialSnapshot { trial_state: Record; } +/** Read-only view of the execution recorder, available to Resolvers and finalizers. */ export interface RuntimeView { getReducedRows(): ReducedTrialRow[]; sumReducedField(field: string): number; } +/** A function that computes a value at runtime from the current trial state. */ export type Resolver = (snapshot: TrialSnapshot, runtime: RuntimeView) => T; +/** + * A value that may be literal, a {@link StateRef} to another unit's state, + * or a {@link Resolver} function evaluated at runtime. + */ export type Resolvable = T | StateRef | Resolver; export type StimSpec = @@ -117,6 +141,7 @@ export interface SpeechStimSpec extends BaseStimSpec { volume?: number; } +/** Configuration for keyboard response capture during a trial stage. */ export interface ResponseConfig { keys: string[]; correct_keys?: string[]; @@ -138,6 +163,10 @@ export interface TrialContextSpec { stim_features?: Record | null; } +/** + * A single stage within a compiled trial (e.g. show stimulus, capture response, wait). + * Built by {@link StimUnit} and executed by the jsPsych plugin. + */ export interface CompiledStage { unit_label: string; op: "show" | "capture_response" | "wait_and_continue"; @@ -163,6 +192,7 @@ export type TrialFinalizer = ( helpers: TrialFinalizeHelpers ) => void; +/** A fully compiled trial containing an ordered list of stages, built by {@link TrialBuilder}. */ export interface CompiledTrial { trial_id: number | string; block_id: string | null; @@ -173,6 +203,7 @@ export interface CompiledTrial { finalizers: TrialFinalizer[]; } +/** One row of raw JSONL output, recorded per-stage during execution. */ export interface RawStageRow { trial_id: number | string; block_id: string | null; @@ -202,8 +233,10 @@ export interface RawStageRow { extra_data: Record; } +/** One row of the reduced CSV output, recorded per-trial after finalization. */ export type ReducedTrialRow = Record; +/** Structured result of parsing a psyflow YAML config file. */ export interface ParsedConfig { raw: Record; task_config: Record; diff --git a/src/index.ts b/src/index.ts index 0bbfbc3..d8efe8d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,14 @@ +/** + * psyflow-web — browser runtime for auditable psychology experiments. + * + * **For task authors**: use {@link TaskSettings}, {@link BlockUnit}, {@link StimBank}, + * {@link StimUnit}, {@link TrialBuilder}, and {@link SubInfo} to define trials. + * + * **For runtime consumers**: use {@link runPsyflowExperiment} or {@link mountTaskApp}. + * + * @module + */ + export { parsePsyflowConfig } from "./core/config"; export { TaskSettings } from "./core/TaskSettings"; export { BlockUnit } from "./core/BlockUnit";