Skip to content

feat: resume on page reload + first-class jsPsych.state (9.0) - #3713

Draft
jodeleeuw wants to merge 8 commits into
mainfrom
feature/resume-on-reload
Draft

feat: resume on page reload + first-class jsPsych.state (9.0)#3713
jodeleeuw wants to merge 8 commits into
mainfrom
feature/resume-on-reload

Conversation

@jodeleeuw

@jodeleeuw jodeleeuw commented Aug 7, 2026

Copy link
Copy Markdown
Member

Resolves #3573

What this does

Two related additions, targeted at 9.0 (the changeset is a major bump):

  1. Resume on page reload (opt-in): jsPsych persists a session log to localStorage (or a custom storage adapter) after every trial. On reload, the timeline replays the log to rebuild experiment state and continues live from the interruption point.
  2. jsPsych.state: a first-class, JSON-serializable state store on the jsPsych instance. It works in-memory for every experiment, and becomes durable across reloads when resume is enabled. jsPsych itself uses it for the RNG seed, addProperties() values, and the manual progress-bar position (reserved keys _rng_seed, _data_properties, _progress, _resumes).
const jsPsych = initJsPsych({
  resume: {
    key: "my-experiment-v1",          // changing the key invalidates saved sessions
    storage: window.localStorage,      // default; pluggable getItem/setItem/removeItem
    max_age: 86400000,                 // optional; partial sessions older than this start fresh
    incomplete_session: "resume",      // "resume" (default) | "restart" | "block"
    completed_session: "restart",      // "restart" (default) | "block"
    block_message: "<p>...</p>",       // shown when a policy refuses to run
    on_resume: (data) => { ... },      // fires once at the replay→live transition
  },
});

jsPsych.state           // general-purpose state store; persists across reloads when resume is on
jsPsych.resetSession()          // discard the saved session and restart in place, callable from anywhere

{ type: jsPsychInitializeCamera, run_on_resume: true }  // new universal trial parameter

Design: record–replay instead of a serialized cursor

The v8 timeline's position lives in the async call stack of Timeline.run(), so there is no serializable cursor. Instead, the session log records the outcome of every nondeterministic decision — timeline-variable orders from generateTimelineVariableOrder(), conditional_function/loop_function results, and trial results captured after on_finish/extension mutations. On reload, the timeline runs from the top in a replay mode that consumes the log: trials short-circuit (no plugin execution, no callbacks), their logged results flow through the normal data.write() path so all internal structures rebuild naturally, and execution transitions seamlessly to live when the log is exhausted. This forks at the leaf exactly like simulation mode; the control-flow engine is untouched.

Key behaviors:

  • Replayed trials keep their original time_elapsed and trial_index; the experiment clock continues from the persisted elapsed time.
  • run_on_resume: true (default false) re-executes a trial live during replay — for environment-establishing trials (fullscreen, initialize-camera, connections). Its fresh result replaces the logged one.
  • Closure state doesn't survive a reload by design (callbacks don't re-run during replay, so side effects like server saves fire exactly once). The escape hatches are jsPsych.state and the on_resume hook.
  • Graceful degradation everywhere: mismatched key, unknown format version, corrupted or misaligned log entries, and storage failures all warn and fall back to live execution — never a crash.

RNG auto-seeding (behavior change for all experiments)

initJsPsych() now always seeds Math.random (alea via seedrandom) and stores the seed in jsPsych.state._rng_seed; a saved session restores its seed. This patches the page-global Math.random for every jsPsych experiment, whether or not resume is used — called out prominently in the changeset and docs.

Seeding happens at construction, before user code builds the timeline, deliberately: it makes build-time randomization (shuffling a stimulus list while constructing the timeline) reproducible, so a reloaded page reconstructs an identical timeline and replay stays aligned. Without this, any build-time shuffle would derail resume at the first mismatch. Precedence: a user-set seed (via setSeed()) > seed restored from a saved session > freshly generated. New jsPsych.randomization.getSeed() accessor.

Core state now persisted via jsPsych.state

  • jsPsych.data.addProperties() values are stored under state._data_properties and restored on resume, so they apply to live trials after the resume point (previously they'd be silently lost).
  • A manually set jsPsych.progressBar.progress is stored under state._progress and restored on resume (auto-update mode recomputes from the timeline and is unaffected).

Return policies and expiry

The resume option controls what happens when a participant comes back: incomplete_session decides the fate of an unfinished session ("resume" replays it, "restart" discards it, "block" refuses to run — enforcing single-sitting completion), and completed_session decides whether finishing once locks the experiment ("block" keeps a minimal completion marker with the log and participant data dropped; "restart" clears storage as before). Blocked runs display block_message and fire no callbacks. max_age expires stale partial sessions on the "resume" path only — a blocking record deliberately never expires. The docs state plainly that lockout via browser storage is a deterrent, not a security measure. Every successful resume also appends {trial_index, time_away, resumed_at} to the reserved _resumes state key, so researchers can see that an interruption happened and how long the wall-clock break was — information the continuous experiment clock otherwise erases.

Testing

packages/jspsych/tests/core/resume.test.ts uses shared in-memory storage across two JsPsych instances to simulate reload: round-trip resume, randomization reproduced without re-sampling, conditional_function/loop_function not re-invoked, run_on_resume, state restoration and on_resume firing exactly once, record_data: false replay, session cleared on completion/abort, max_age expiry on both sides of the boundary, the full incomplete_session/completed_session policy matrix (blocked runs fire no callbacks and leave blocking records in storage; resetSession() unblocks both record kinds and restarts in place; config-changed markers discarded), key/format-version mismatches, corrupted-session degradation, storage unavailable, feature-off no-op, seed persistence with build-time shuffles, user-seed precedence, _data_properties restoration, and manual progress restoration. packages/jspsych suite: 35 suites / 442 tests passing; tsc clean.

Review notes / open questions

  • clearSavedSession() inside on_finish footgun — resolved: resetSession() (which replaced it) suppresses the in-flight trial's persist and restarts the experiment in place, making it safe to call from any callback. It also restarts blocked and completed pages without a reload. Note: an in-place restart reuses the timeline arrays already built at page load (same _rng_seed), so build-time randomization repeats until a real reload — documented.
  • Deferred follow-ups: checkpoint markers (resume at task boundaries, per Enable recovery on page reload #3573 discussion), plugin-facing state writes + re-apply hook for the resize/virtual-chinrest scale transform, camera/mic deviceId persistence for smoother run_on_resume re-runs, a way to detect a resumed session outside on_resume.

🤖 Generated with Claude Code

Adds an opt-in `resume` option to initJsPsych() that persists a session
log (timeline variable orders, conditional/loop function outcomes, and
trial results) plus a user state object to localStorage after every
trial. On reload, the timeline replays the log — skipping plugin
execution and callbacks — to rebuild data and position, then continues
live at the interruption point. Includes jsPsych.resume.state/clear(),
an on_resume callback, and a run_on_resume universal trial parameter
for environment-establishing trials.

Resolves #3573

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8e643ea

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
jspsych Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📦 Preview build ready

Built from PR head 8e643ea and published at 9898ada on branch preview/pr-3713.
URLs below are pinned to an immutable commit SHA, so they are safe to share and are cached permanently by jsDelivr.

Changed packages: jspsych

Quick-start HTML:

<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/jspsych/dist/index.browser.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/jspsych/css/jspsych.css">
<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-html-keyboard-response/dist/index.browser.min.js"></script>
All package URLs
  • @jspsych/extension-mouse-trackinghttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/extension-mouse-tracking/dist/index.browser.min.js
  • @jspsych/extension-record-videohttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/extension-record-video/dist/index.browser.min.js
  • @jspsych/extension-webgazerhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/extension-webgazer/dist/index.browser.min.js
  • jspsychhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/jspsych/dist/index.browser.min.js
  • @jspsych/plugin-animationhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-animation/dist/index.browser.min.js
  • @jspsych/plugin-audio-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-audio-button-response/dist/index.browser.min.js
  • @jspsych/plugin-audio-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-audio-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-audio-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-audio-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-browser-checkhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-browser-check/dist/index.browser.min.js
  • @jspsych/plugin-call-functionhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-call-function/dist/index.browser.min.js
  • @jspsych/plugin-canvas-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-canvas-button-response/dist/index.browser.min.js
  • @jspsych/plugin-canvas-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-canvas-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-canvas-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-canvas-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-categorize-animationhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-categorize-animation/dist/index.browser.min.js
  • @jspsych/plugin-categorize-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-categorize-html/dist/index.browser.min.js
  • @jspsych/plugin-categorize-imagehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-categorize-image/dist/index.browser.min.js
  • @jspsych/plugin-clozehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-cloze/dist/index.browser.min.js
  • @jspsych/plugin-external-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-external-html/dist/index.browser.min.js
  • @jspsych/plugin-free-sorthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-free-sort/dist/index.browser.min.js
  • @jspsych/plugin-fullscreenhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-fullscreen/dist/index.browser.min.js
  • @jspsych/plugin-html-audio-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-html-audio-response/dist/index.browser.min.js
  • @jspsych/plugin-html-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-html-button-response/dist/index.browser.min.js
  • @jspsych/plugin-html-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-html-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-html-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-html-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-html-video-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-html-video-response/dist/index.browser.min.js
  • @jspsych/plugin-iat-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-iat-html/dist/index.browser.min.js
  • @jspsych/plugin-iat-imagehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-iat-image/dist/index.browser.min.js
  • @jspsych/plugin-image-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-image-button-response/dist/index.browser.min.js
  • @jspsych/plugin-image-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-image-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-image-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-image-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-initialize-camerahttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-initialize-camera/dist/index.browser.min.js
  • @jspsych/plugin-initialize-microphonehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-initialize-microphone/dist/index.browser.min.js
  • @jspsych/plugin-instructionshttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-instructions/dist/index.browser.min.js
  • @jspsych/plugin-maxdiffhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-maxdiff/dist/index.browser.min.js
  • @jspsych/plugin-mirror-camerahttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-mirror-camera/dist/index.browser.min.js
  • @jspsych/plugin-preloadhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-preload/dist/index.browser.min.js
  • @jspsych/plugin-reconstructionhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-reconstruction/dist/index.browser.min.js
  • @jspsych/plugin-resizehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-resize/dist/index.browser.min.js
  • @jspsych/plugin-same-different-htmlhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-same-different-html/dist/index.browser.min.js
  • @jspsych/plugin-same-different-imagehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-same-different-image/dist/index.browser.min.js
  • @jspsych/plugin-serial-reaction-time-mousehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-serial-reaction-time-mouse/dist/index.browser.min.js
  • @jspsych/plugin-serial-reaction-timehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-serial-reaction-time/dist/index.browser.min.js
  • @jspsych/plugin-sketchpadhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-sketchpad/dist/index.browser.min.js
  • @jspsych/plugin-survey-html-formhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-survey-html-form/dist/index.browser.min.js
  • @jspsych/plugin-survey-likerthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-survey-likert/dist/index.browser.min.js
  • @jspsych/plugin-survey-multi-choicehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-survey-multi-choice/dist/index.browser.min.js
  • @jspsych/plugin-survey-multi-selecthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-survey-multi-select/dist/index.browser.min.js
  • @jspsych/plugin-survey-texthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-survey-text/dist/index.browser.min.js
  • @jspsych/plugin-surveyhttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-survey/dist/index.browser.min.js
  • @jspsych/plugin-video-button-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-video-button-response/dist/index.browser.min.js
  • @jspsych/plugin-video-keyboard-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-video-keyboard-response/dist/index.browser.min.js
  • @jspsych/plugin-video-slider-responsehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-video-slider-response/dist/index.browser.min.js
  • @jspsych/plugin-virtual-chinresthttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-virtual-chinrest/dist/index.browser.min.js
  • @jspsych/plugin-visual-search-circlehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-visual-search-circle/dist/index.browser.min.js
  • @jspsych/plugin-webgazer-calibratehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-webgazer-calibrate/dist/index.browser.min.js
  • @jspsych/plugin-webgazer-init-camerahttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-webgazer-init-camera/dist/index.browser.min.js
  • @jspsych/plugin-webgazer-validatehttps://cdn.jsdelivr.net/gh/jspsych/jsPsych@9898ada5c0e174f86ea3b7fd4482d75343cd64ed/packages/plugin-webgazer-validate/dist/index.browser.min.js

Last updated 2026-08-08 01:14 UTC for PR head 8e643ea.

…operties, and progress integration

Promotes the resume feature's state object to a first-class jsPsych.state
property (jsPsych.resume now only has clear()). jsPsych seeds Math.random
at construction and stores the seed in state.rng_seed, making build-time
randomization reproducible across reloads so resumed sessions reconstruct
identical timelines. addProperties() values and manually set progress bar
positions are persisted via the reserved state keys data_properties and
progress, and restored on resume. Targets 9.0 (changeset bumped to major).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jodeleeuw jodeleeuw changed the title feat: resume experiment after page reload (record-replay session log) feat: resume on page reload + first-class jsPsych.state (9.0) Aug 7, 2026
github-actions Bot pushed a commit that referenced this pull request Aug 7, 2026
Renames the internally written state keys to _rng_seed,
_data_properties, and _progress to keep them out of the way of
user-defined keys while leaving them accessible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Aug 7, 2026
…sion()

With state promoted to jsPsych.state, the resume namespace held only
clear(). A top-level clearSavedSession() method follows the existing
verb-style instance methods and removes the confusing adjacency with
jsPsych.resumeExperiment().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Aug 7, 2026
@jodeleeuw

Copy link
Copy Markdown
Member Author

@jadeddelta and @becky-gilbert I'd love to get your thoughts on the API design here. Don't worry about a technical review.

Adds a savedAt timestamp to the persisted session and a max_age option
that discards expired partial sessions. Adds return-policy options:
incomplete_session ("resume" | "restart" | "block") and
completed_session ("restart" | "block"), with a configurable
block_message shown when a policy refuses to run the experiment.
Blocking a completed session writes a minimal completion marker with
the log and state dropped; blocking an incomplete session leaves the
stored record untouched so the lockout persists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Aug 7, 2026
Each successful resume appends {trial_index, time_away, resumed_at} to
jsPsych.state._resumes. time_away captures the wall-clock break
duration, which is otherwise invisible because the experiment clock
runs continuously across the interruption.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Aug 7, 2026
resetSession() discards the saved session and restarts the experiment
without a page reload, callable from any phase: before run() it only
clears, mid-run it aborts the timeline and re-runs it from the top, and
on a blocked or finished page it runs the experiment again. The restart
resets everything jsPsych controls (data, state, clock, progress bar)
while keeping _rng_seed (the built timeline came from its draws) and
addProperties values. The in-flight trial's pending persist is
suppressed, closing the clear-inside-on_finish footgun.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Aug 7, 2026
Fixes an inaccurate count description for _resumes, clarifies the
build-time vs run-time randomization distinction, makes the policy
table cells parallel, restructures the dense resume option reference
row into per-field sentences, and renames the seed data property in
examples so the underscore reserved-key convention is not implied to
apply to trial data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Aug 8, 2026
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.

Enable recovery on page reload

1 participant