Changelog¶
What each release added, changed and fixed, in one line per item. The notes
behind each line, and the releases before 0.28.0, are in
CHANGELOG.md.
Unreleased is what is on main and not yet on PyPI.
Unreleased¶
Added
- Export only the screens you pick from trials that span several screens: Screens in the Export bundle, render --screens, and the screens argument of api.render_parent_trial. (#402)
0.37.1 — 2026-10-07¶
Added
- Choose where each legend sits: Figure & canvas → Legends places the Compare, saccade-type, colour and size-key legends above, below, beside or inside the plot, stacked or in a row, at any text size. (#384)
Changed
- The plot rail's Flag fixations section is now Filters & highlights, with a filter icon. (#396)
Fixed
- The desktop app's Update & restart is sturdier: a failed download says why (a missing file, a full disk) instead of asking whether you are offline, Cancel works until the moment it restarts, two updates started at once no longer collide, and About stops mentioning the last update after a week. (#394)
- Changing trials no longer leaves a scanpath's fixation index range narrowed to a few fixations, and the plot now says so whenever the index range hides some of a trial's fixations. (#395)
- A line's first word now sits at the left edge of its box, as it was shown in the experiment, instead of being centred in a box that carries only the gap after it. (#397)
- Stretch text to AOI boxes now reads the exact font from monospace word boxes padded with half a space on each side, instead of drawing the text several percent too small. (#397)
- The hosted demo follows each release again; it had stayed on v0.35.0. (#400)
0.37.0 — 2026-10-07¶
Added
- Help → About can check for updates: it shows whether a newer release is out and the command that updates your install, or the download for the desktop app; also scanpath-studio version --check and api.check_for_updates(), and only ever when asked. (#139)
- The desktop app updates itself: Help → About → Check for updates → Update & restart downloads, checks and tests the new version, then restarts into it, and puts the old one back if it doesn't start. (#385)
Changed
- Every build now reports its own version: between releases it is a development build such as 0.35.0.post3+g8f18219 (three commits after 0.35.0), shown in About, scanpath-studio --version, crash reports and exports, and as scanpath_studio.version; the hand-set release number is scanpath_studio.release. (#139)
Fixed
- Dataset downloads now trust the certificates your operating system trusts, so they no longer fail with CERTIFICATE_VERIFY_FAILED on a python.org Python on macOS or behind a network that inspects HTTPS. (#391)
0.36.0 — 2026-10-07¶
Added
- The chips above the plot can be hidden from the ✏️ menu at the end of the trial row, which keeps their fields for when you show them again; a share link carries the choice. (#373)
- A Windows installer (ScanpathStudio-windows-x86_64-setup.exe): one download that installs the desktop app for your account, adds it to the Start menu and uninstalls from Settings → Apps, with no unzipping. The .zip is still there for running without installing. (#387)
Changed
- The dataset and trial dropdowns (and Compare's) open as wide as their longest option, so long names are read in full. (#381)
- The scanpath plot is centred in its column and grows to fill the column when the window has room, rather than stopping at its true size; the chip table keeps each trial on one line. (#381)
- The screen picker sits at the right end of the trial row as a compact dropdown with ◀ ▶, instead of a row of its own under each trial row, with the sort, filter and chips menus after it at the row's end. (#381)
- The Export subtab asks Self-contained HTML only once HTML is the chosen format, under that format — the bundle asks its own. (#382)
Removed
- The Export subtab's Download this comparison as a bundle expander. (#382)
Fixed
- A metadata table uploaded to one dataset no longer attaches itself to the next dataset you open; switching datasets now empties the metadata uploaders. (#380)
- A UTF-16 table, such as an EyeLink Data Viewer report, now reads its column names instead of showing every column as "Unnamed". (#383)
0.35.0 — 2026-10-07¶
Added
- The dataset setup guide now ends on a step about saving: Add dataset, and Download setup file to reuse the mapping next time. (#365)
- The Corpus Analysis address names the open section (
?corpus_subtab=Per+reader), so a bookmark or a copied link reopens it. (#369) - Screen readers announce the scanpath figure, the animated replay, the Corpus Analysis charts and the Share link box by name. (#369)
- Help → About links to Discussions → Q&A for questions and to the bug-report form, and the FAQ says where to ask, how to report a bug, and that a 0.x release may change links, settings, the API and the CLI. (#371)
- An unexpected error now says it is most likely a bug in the app and links to the bug-report form and Q&A, above the error details to paste into the report. (#376)
- Export → Current figure, save_figure and render take a print width (mm or in) and DPI, written into the PNG; Share → Code writes the same pixels as the download. (#377)
- Data Management → Saved on this computer names everything it restores, and Clear what is saved… deletes it after a confirmation and starts the app over. (#377)
Changed
- When the fixation table maps a Word/IA ID, fixations now count for exactly that word, and a blank means no word; without one, a fixation counts for the word box it falls in and is no longer snapped to a word up to 50 px away. (#352)
- In Compare, the Saccades popover now lays out like Fixations: the Line group becomes Scanpath A's colour, style and width, with Scanpath B's group under it, instead of greyed figure-wide rows followed by a duplicate per-scanpath block. (#361)
- In Compare, the Word boxes popover now has a Scanpath A and a Scanpath B group, each with its own line and fill colour (the new box_fill_color style key, on the Share link, CLI --style-a/--style-b and the API too). (#363)
- The hosted app's run-locally tip now says a local install is faster and handles much larger datasets. (#365)
- The add-dataset wizard's Setup help popover is now a compact two-row menu (Show setup guide · More documentation). (#365)
- Edit dataset now sits under the open dataset's description and checks, just above its subtabs, instead of on the heading's line. (#366)
- The Data page's Participants, Trials and Texts tabs list the ids in your data, with counts, when no separate table is attached — and the Participants and Trials tabs add every column that holds one value per reader or trial. (#367)
- In Compare, the Raw gaze popover now has a Scanpath A and a Scanpath B group, each with its own sample colour (the new raw_gaze_color style key, also on the Share link, CLI --style-a/--style-b and the API). (#368)
- Debug opens from ❓ Help → About as a resizable drawer on the right, beside the view it describes. (#369)
- Typing a dataset's description or a built-in dataset's new name, or flipping the editor's character-box toggle, no longer reruns the page; the value is applied when you save. (#369)
- Name fields (a new dataset, an edited one, a saved or renamed design) no longer accept an empty name; clearing one keeps the last name. (#369)
- Requires Streamlit 1.65. (#369)
- In Compare, the Heatmap popover now has a Scanpath A and a Scanpath B group, each with its own colour scale on the shared range, and a colour bar each when they differ (the new heatmap_colorscale style key, also on the Share link, CLI --style-a/--style-b and the API). (#370)
- The README, Getting started and the hosted demo's tips now put the desktop app first for working with your own data. (#371)
- The documentation site is now published with each release, so it describes the version pip installs, and each GitHub release page opens with what the tool is and how to get it. (#371)
- On the hosted demo, clicking a public corpus the server doesn't hold (PoTeC, OneStop) now says in a dialog that it opens in the desktop app or a pip install, instead of opening the demo in its place. (#371)
- The README's replay and two-reader animations, the app demo, and the docs screenshots were re-captured from the current app. (#372)
- A misspelt CLI flag gives a short error with a suggestion, options put before the command say where they belong, render takes --word-boxes, --heatmap, --no-text and US/UK spellings, and cache prints real plurals. (#377)
- Compare names its pickers Scanpath B from and Scanpath B, titles side-by-side panels A and B, and shows its legend by default; Comparisons matches on the same text or the same participant. (#377)
- Hovering a fixation names the word it landed on: Fixation 41 · 336 ms · on “Droppings!” (word 27). (#377)
- The API and CLI draw the app's Scanpath design by default (no heatmap, word boxes or fixation numbers unless asked, A/B legend on), and the bundled demo uses its recorded screen; reproduction code written before this may need those layers turned on. (#377)
- Debug moved from the Help menu into About; the welcome tour no longer starts over a restored session; the FAQ says where data goes for where the app runs. (#377)
- Only the bundled demo highlights its answer span by default, and a highlighted figure carries a key saying what is marked. (#377)
- Corpus Analysis says participant, not reader: the Per participant subtab (old links to Per reader still open it), its labels and counts. (#377)
- Mapped fields are named by their role (Participant, Text, Trial) with the source column in a tooltip, and a trial is written the same way in the picker, Comparisons, Annotations and Corpus Analysis. (#377)
- The trial chip Total reading time is now Total fixation time, beside a new Trial duration chip; the replay's clock is labelled Trial time. (#377)
- Labels, help and messages across the app, CLI and docs use one term per concept (participant, dataset, settings file, Flag fixations) and US spelling. (#377)
- The add and edit screens call the word table Words (interest areas), say which EyeLink report goes in each row, detect an item column as Text ID and keep condition columns. (#377)
Removed
- Groups → Paired summary bars no longer draws error bars, which pooled words across participants; its title follows the Aggregate. (#377)
- The tutorials' Don't auto-show checkbox, which no setting read, is gone. (#377)
Fixed
- Switching datasets no longer leaves the text highlight on another dataset's column (the demo read IA_SKIP after a trip through OneStop). (#358)
- The computations reference now describes run/pass columns and per-reader normalization as the code computes them. (#359)
- Figure titles and hovers spell a column ending in
_idas "ID" ("Participant ID", not "Participant Id"). (#359) - The code
render --print-codewrites for--words/--fixationsnow passes on the--word-schema/--fix-schemamapping the files were read with. (#359) - Comparing with a trial whose id already reads like a de-duplicated label ("x [p2]") no longer hides another reader's trial of the same id: every candidate keeps its own entry in the Compare to picker. (#360)
- A comprehension_questions value of the wrong shape (an object, nulls, nested values) no longer crashes the trial: unreadable records are skipped with one warning, and the Stimulus & Context tab now renders only while it is open. (#360)
- Importing a designs file now checks every setting the way a link is checked: an invalid value is skipped and named in the import message instead of crashing the app when the design is applied, and a file from a newer version says so. (#360)
- Questions, answers and context fields in Stimulus & Context are shown as the literal text in the data: Markdown, links, images and HTML in a value no longer format the panel or make the browser fetch a remote image. (#360)
- Span and answer flags written as the text "False" now read as false: false span rows are no longer highlighted or counted as fixated, an incorrect answer no longer shows as correct, and a true/false filter keeps both of its classes. (#360)
- A stimulus image whose origin is (0, 0) in the words table now sits at (0, 0) instead of taking the fixation table's origin. (#360)
- Files that share a name in different folders of a ZIP, or of a multi-file upload, now keep distinct source_file labels (reader-a/fixations, reader-b/fixations), so mapping that label as participant or trial no longer merges two readers into one. (#360)
- The Synthetic sample's row in Available datasets shows its counts on a server with no recovery cache, such as Streamlit Community Cloud, instead of Not loaded. (#364)
- The package summary on PyPI no longer says the app computes reading measures. (#371)
- Screen readers hear plain control names (no markdown or icon words), and the figure can be reached with Tab. (#377)
- Corpus Analysis captions name only the views offered, Groups keeps its comparison across subtabs, the trend line breaks at filtered-out trials and Per text opens on the trial viewed in Scanpath. (#377)
- A ZIP holding both fixation and interest-area reports is split by kind: each table row reads only its own reports and says which files it left out. (#377)
- At 1024–1280 px the plot rail keeps its labels and design names, and wide tables show that they scroll. (#377)
- The Python API accepts any spelling of a figure option's value (case, dashes, the CLI's names) and raises on an unknown one instead of drawing the default. (#377)
- A Share link to a dataset you added names it, and a recipient who doesn't have it is told which dataset is missing and how to get it, instead of the view being applied to another dataset. (#377)
- Share → Code writes only options that change the figure, None as None, and a palette only when the colors match it. (#377)
- Corpus Analysis and word exports leave a skipped word out of FFD, FPRT, RPD and single-fixation duration instead of counting an imported 0 (TFD keeps its 0); a blank imported second-pass duration is 0 where the fixation count is known. (#377)
- A palette or design preset no longer reverts after a rail popover has been opened, an annotation no longer jumps the view to trial 1, and each dataset reopens on the trial it was left on. (#377)
0.34.0 — 2026-10-06¶
Added
- The Illustration label's text can be edited (rail, link, settings file,
render --illustration-text,illustration_text=), and title and caption have their own switches. (#349) - Word boxes: the outline has its own opacity (rail, share link,
--word-box-line-opacity,word_box_line_opacity=), and each opacity now sits beside its colour. (#349) - Data checks also report infinite fixation durations and onsets, word boxes at infinity, and per-screen screen sizes that are infinite or 0 or less. (#351)
Changed
- Data Management's Status column says Loaded (opens at once) or Available (opening reads its files) instead of Ready. (#349)
- Figure & canvas: Whole monitor is now an unticked Crop to data box, the monitor size and the px/degree line left the rail (set on the Data page), and the axes row warns that fields other than x / y draw fixation markers only. (#349)
- Both colour scales (fixations and heatmap) open in Blues, and colour bars are shown by default. (#349)
- Heatmap: Duration mass is removed (links and settings files naming it open Interpolated); Interpolated gets a Blur row — Auto (showing the σ it uses) or a fixed σ in px (
heatmap_sigma_px,render --heatmap-sigma); the colour range starts at 0 and is offered for Fixation count too. (#349) - The fixations and the heatmap each have their own colour bar settings (show, orientation, tick angle and size), under their layer in the rail;
rendertakes--no-fixation-colorbar/--fixation-colorbar-*and the heatmap equivalents, and links or settings files using the old shared settings apply them to both. (#349) - Shorter help texts throughout the plot controls. (#349)
- The command-line help, API reference, error messages and computation register no longer show internal tracker IDs. (#354)
Removed
- The px-per-degree figure is no longer shown in the Recording setup forms, and the export's saccade table no longer converts amplitudes to degrees. (#349)
- Measures the app computes itself are held back until validated: the reading-measure, summary, preprocessing and analysis-table API functions, the
analyzecommand, the export bundle's measure family, and the computed Corpus Analysis views (Reading summary, Progressive vs regressive, Landing-position curve, Reader summary table) now needSCANPATH_EXPERIMENTAL=1. (#354)
Fixed
renderaccepts v0.33.0's--colorbarsand shared--colorbar-orientation/--colorbar-tickangle/--colorbar-tickfont-sizeagain, the last three setting both colour bars unless a bar's own flag is given. (#351)- A figure's title, caption, Illustration label, word-heatmap title and the names of compared scanpaths are drawn as written: angle brackets,
&and%{…}no longer act as Plotly markup, and only a real newline starts a new caption line. (#351) - An infinite position, duration, onset or word-box size no longer stretches a figure's axes or crashes it or its replay; the figure leaves such a row out (or times it by duration), and a per-screen screen size that is not finite and positive falls back to the dataset's own. (#351)
- The plot rail shows "Word boxes" in full at a 1280-pixel window. (#351)
scanpath-studio cacheno longer prints Streamlit's "No runtime found" warnings, the CLI docs list thecheckcommand, and the MultiplEYE and benchmark-corpus loaders are no longer advertised in the package's public names. (#354)- Corpus Analysis › Per reader › Fixation duration over time opens on a per-fixation measure instead of an empty notice. (#354)
- An Export bundle's README mentions
annotations.jsononly when the bundle holds it, that is, when an exported trial has an annotation. (#355) - A Share link to a comparison whose second reader or trial id contains a colon now restores that comparison. (#355)
- The export's file path pattern must stay inside the ZIP: an empty pattern, a leading
/or drive, a backslash, and an empty,.or..folder or file name are refused before anything is rendered, in the app and bybulk_export. (#355) - Metadata tables saved on this computer that can't be read back are kept as they are, said under Saved on this computer with Retry and Remove from cache, instead of being deleted by the next save. (#355)
- An infinite value in a participant, trial or text metadata table counts as no value, so its filter opens instead of failing, and Keep unknown values decides whether those records stay. (#355)
- Ids that only differ as "1" and "1.0" in one column stay two readers or trials, and a participant or trial id that is only spaces or tabs is reported and left out like a missing one, in raw gaze too. (#355)
- Compare's A/B trial table fits its column — a long trial id wraps instead of pushing the last columns out of view, and a table still too wide shows its scrollbar — and a value both scanpaths share is written in both rows instead of one merged cell that read as B's left blank. (#356)
- The dataset table offers Remove only for datasets you added: on the demo and the public corpora it only hid the row for the session, with no way back. The table also sits a little lower under the Data Management title. (#356)
- The rail's design-preset highlight goes back to the preset when Compare is switched on and then off again, instead of staying on Custom. (#356)
- Export's HTML option is labelled Self-contained HTML, and its help says it makes the file open without an internet connection. (#356)
- Corpus Analysis › Per text › Per-reader profiles leaves room for each reader's title, which no longer sits on the panel above. (#356)
- With saving switched off at launch, Saved on this computer says so in plain words, and names the setting only in a small line under it. (#356)
- The welcome tour's last step rings the whole nav once instead of drawing a bracket round each link, and the ring goes when the tour ends. (#356)
- The add-dataset screen's setup guide counts its three parts (Part 2 of 3) instead of its cards, leaves room beside the form on a wide screen instead of covering the upload rows, and its help pill is a compact Setup help next to Cancel. (#356)
0.33.0 — 2026-10-04¶
Added
scanpath-studio checkandcheck_data_health: a Data checks line on the Data page flags impossible values (≤ 0 ms fixations, missing positions, zero-area word boxes). (#339)- Compare draws raw gaze, and each reading's word-box outline can be recoloured. (#311, #345)
- Export bundles list their files in
index.csv, preview their size before building, can be stopped, and can be saved as self-contained offline HTML. (#337) - Edit dataset previews mapping changes before saving, can add a raw-gaze table, and lets you set your own recording setup for built-in corpora. (#337)
- Corpus Analysis shows which filters apply, explains each measure in place, and saves a JSON recipe beside every table download. (#328, #335)
- Add dataset offers downloadable example tables; Color by offers any numeric column a dataset kept. (#328, #339)
- Public datasets download into one folder you choose; OneStop is now four datasets, one per reading regime. (#288, #295)
Changed
- The app, API and exports name columns as they are in your files;
load_scanpath_data(names="canonical")gives the internal names. (#333, #338) - Fixation markers use one fixed duration scale by default, so a duration is the same size in every figure. (#335)
- Word boxes have their own rail section with outline and fill colours. (#343)
- Faster: plot setting changes on large datasets (~3×), the animated replay (~3×), and switching back to the previous dataset. (#289, #339, #342)
- Edit dataset holds every change until Save changes and opens below the dataset list; the Data tab is now Data Management. (#319, #337)
- Trial chips are a compact table (two rows in Compare) and trial ids read part by part. (#303, #305, #313, #326)
- Public corpora download at a pinned, size-checked version. (#321)
- Shorter docs with fresh screenshots; the app uses its Material icons throughout. (#285, #302)
Removed
- Corpus Analysis → Groups no longer runs significance tests; it reports group means and their difference. (#335)
Fixed
- Compare: each reading keeps its own screen, canvas and stimulus image; categorical colouring and Animate + Compare work in every layout. (#337, #339, #340)
- IDs no longer merge: zero-padded ids, underscores in composite ids, and PoTeC's trial id (now reader + text). (#291, #335, #337)
- Corpus Analysis measures are counted correctly: skip / regression rates, reading speed without timestamps, per-screen sentences, and Groups cohorts. (#324, #335)
- Edit dataset's Cancel, Estimate, Save setup and added tables behave as expected. (#291, #337)
- Heatmaps, colour bars, GIF/MP4 frames, arc saccades and word labels draw correctly in more cases. (#294, #339, #340)
- Share links and settings files restore Compare and Animate state, and say why when a reading can't be opened. (#337, #339)
- A damaged dataset in the recovery cache no longer blocks the others; a corrected large table no longer reloads its old values. (#314, #339)
- The macOS folder button no longer crashes the app; icon-only buttons have screen-reader names. (#298, #324, #328)
Security
- Figures no longer offer Plotly's "Share chart…" button, which uploaded data to Plotly Cloud. (#310)
0.32.1 — 2026-10-01¶
Added
- Corpus Analysis → Groups can now split or filter a cohort by an attached trial- or text-metadata field (marked 📋 and 📄), alongside reader fields and trial conditions. (AN-31)
Changed
- The README and docs-home app demo recording is re-recorded from the current app. (ENG-87)
- Export,
analyzeandapi.analysis_tableswrite only the reading measures the dataset brought and compute none, as Corpus Analysis does; the Export bundle's Mega-table option is replaced by a "Combine all trials into one file" toggle that writes each chosen table once with every trial stacked in it. (EXP-23) - A replay's page is now a fraction of its old size: frames travel packed and are rebuilt in the browser, so the demo's longest trial loads as 0.3 MB instead of 12 MB (0.7 MB instead of 66 MB at the finest frame grid), in the app, the saved HTML and the docs gallery alike. (PERF-17)
Fixed
- A missing corpus's "isn't here yet" note no longer carries over to the next dataset you open after a load that stopped at a column-mapping problem. (BUG-96)
- The bundled demo now holds only readers it has fixations for: the third reader, who had word boxes but no fixations, is gone, so every trial the Data page counts can be opened. (DATA-43)
- A Participants, Trials or Texts table now picks its id column by itself whenever the data would, so a Texts table keyed by
unique_paragraph_idneeds no manual pick. (DATA-44) - A text-metadata field picked in the chips now shows its value above the plot, like participant and trial fields; before, the chip silently rendered nothing. (DATA-45)
- Favorites, tags and notes now belong to the dataset they were made on: another dataset that reuses the same participant and trial ids no longer shows them, and annotations saved by an earlier version come back on the added dataset that has their trial, or else on the first dataset you open. (DATA-48)
- A word table with no participant column now attaches to each reading by its trial ID, or by Text ID when the trial IDs include the reader, so the add-dataset screen says how the words attached and stops with a message instead of adding a dataset with no word boxes; with no Text ID mapped, a repeated reading now shares its first reading's text ID, so per-text grouping pools re-readings. (DATA-49)
- The Texts count on the Data page and in a dataset's Stats now counts the text ids in every table, so a dataset whose text id is only on its fixations no longer says it has none. (DATA-50)
- An AOI export whose word-id column is
AOI_IDnow maps it by itself, next toAOI_LABELand theAOI_*box edges, instead of sending you to map it by hand. (DATA-60) - When scanpath B comes from a second dataset, the Share code snippet's Python and CLI halves now both load B's own tables (placeholders
B_WORDS/B_FIXATIONSto point at its files) and state B's screen, instead of looking B's reader up in the first dataset. (EXP-21) - Wizard and Edit-dataset metadata rows keep their Participants / Trials / Texts title visible once a file is attached. (UX-147)
- Long 'Extra fields to keep' chip lists wrap onto more rows instead of scrolling sideways out of sight. (UX-148)
- Two trial filters over columns that read the same, such as
TRIAL_INDEXandtrial_index, no longer share a title: the second names its column, as the chip editor already did. (UX-149) - Narrowing the trial pool no longer switches the design preset to 🛠️ Custom, and a trial with no fixations no longer clears the fixation hover fields you picked. (VIZ-44)
- A dataset recorded as raw gaze alone now opens showing its samples: the Raw gaze layer is on by default when a dataset has no fixations, samples-only trials are pickable, the chips count gaze samples instead of showing zero reading time and fixations, no "derived from raw gaze" Illustration label appears, Animate and Compare fall back to the static figure with the reason, the Export bundle draws the samples and can include them as a table, and
render --raw-gazeandplot_scanpath(raw_gaze=…)accept raw gaze as the only input. (VIZ-45)
0.32.0 — 2026-09-30¶
Added
- In Compare, each scanpath has its own filters — fixation window, short / long / off-text / blink flags and saccade types (CMP-24)
- Column auto-detection catches a vendor prefix or suffix on a known column name (DATA-25)
- The macOS desktop app is signed and Apple-notarized, and ships as a
.dmg(ENG-21) - A Code of Conduct, linked from the README and CONTRIBUTING (ENG-82)
- The desktop app opens in its own window, not a browser tab (ENG-85)
- A title or caption can name a metadata table's fields, and a data table's saved fields, as
{table.field}(EXP-22) - A long wait says what the app is doing: a card with the step, a count, the elapsed time and a Cancel (UX-165)
- Cancel a dataset load, an animation build, Compare's second dataset or a download, and go back to where you were (UX-168)
- A dataset has a description, and the Data page lists every annotation on it — to export, import or delete (UX-174)
- Saved designs export to a file and import from one, and the Export bundle can include the exported trials' annotations (UX-179)
Changed
- Corpus Analysis shows the reading measures your report brings and computes none; map them on the AOI table (AN-32)
- The repository drops the pre-migration tracker archive and seven finished design plans (ENG-84)
- Changelog entries are one file per item in
changelog.d/, written into CHANGELOG.md at release, so parallel PRs no longer conflict; entries are one line, with no Details half (ENG-86) - Add a dataset beside the picker: create manually or import files, with a synthetic sample ready to explore (UX-143)
- A dataset that takes a while to open shows a skeleton of the page and its steps, not a lone banner (UX-166)
- Building an animation counts its frames, and the plot shows a placeholder until the browser has drawn it (UX-169)
- The trial picker walks trials in the order the data has them; Trial ID is a choice in ⇅ (UX-171)
- Compare's A/B legend is larger, so the two readings' names read at a glance (UX-172)
- 📂 Available datasets is a focused table: click a row to open it; Status says whether it is loaded (UX-174)
- The open dataset is described in one plain sentence, with its home page and a note only where a figure reads differently (UX-177)
- Rename a dataset on ✏️ Edit dataset, opened by one Edit dataset button; Status sits beside the name (UX-178)
- 💾 Session is gone: Debug is under ❓ Help, what's saved at the foot of 🗂️ Data, the settings file in 🔗 Share (UX-179)
Fixed
- Choosing the Synthetic sample shows it; the editor opens from its Edit button (BUG-94)
- Removing a dataset you added removes the annotations on its trials, as its confirmation says (BUG-95)
- Attached participant, trial and text tables survive a refresh, and their fields stay in the filters, chips and trial sorting (DATA-38)
- ✅ Save changes on ✏️ Edit dataset no longer strips the word boxes and text from a dataset whose AOI table has no reader column (DATA-39)
- ✅ Save changes on ✏️ Edit dataset keeps an estimated screen, and the estimate reads the data (DATA-46)
- Metadata tables belong to the dataset they were attached to (DATA-47)
- AOI box columns with a prefix or suffix auto-fill even when the table carries two box encodings (DATA-57)
- Clearing the screen fields in a mapping makes the table single-screen again (DATA-59)
- The documented check for IDs an open PR has taken actually reads the PR's changelog (ENG-83)
- The plot-controls rail no longer looks cut off while a figure is being drawn (UX-167)
- Plot controls use the full right column without a separate scrollbar (UX-173)
- A corpus that isn't on this machine no longer shows the demo's counts as its own (UX-174)
- An auto-detected Trial, Participant or Text ID is highlighted, with a ✨ button to confirm it, like every other field (UX-176)
- The trial slider is wider, and a long trial id under it no longer wraps behind the chips (UX-181)
- The Share link is readable in dark mode (UX-182)
- A saved design or Custom view carries every plot control: Compare, each scanpath's styles and filters, the fixation windows and the replay speed (VIZ-47)
0.31.2 — 2026-09-27¶
Changed
export_animationneeds no frame time, andplots.animation_autoplay_frame_durationis gone (BUG-93)
Fixed
- The replay runs in real time: a 20.8 s reading takes 20.8 s to replay, not 26 s (BUG-93)
- Changing the replay's speed or Autoplay no longer rebuilds every frame (PERF-15)
- With 🎬 Animate on, a click that doesn't change the replay no longer reloads it: 0.4 s instead of 19 s at 2,000 frames (PERF-16)
0.31.1 — 2026-09-26¶
Added
- Scanpath Studio has a Zenodo DOI, and every release archives itself there (ENG-75)
- A docs Gallery: each figure drawn from the demo while the site builds, with the code that makes it (ENG-78)
- Cite, Changelog and Glossary pages, and hover definitions for FFD, FPRT, RPD, TFD and the rest (ENG-77)
- The docs list every CLI flag and every figure option, generated from the code (ENG-79)
Changed
- The plot-controls rail drops its tinted card for one divider line (UX-151)
- The README is a landing page: three ways to run it, a docs map, and the detail left to the docs (ENG-76)
- The docs site is organised by reader: Use the app, Automate, Reference, Project (ENG-77)
- The feature guides show the app, in screenshots a script re-captures (ENG-78)
- The docs site navigates without reloading, serves its own fonts, dates each page, and publishes
/llms.txt(ENG-79) - The docs keep only what the beta stands behind: no security audit, no internal pages, no claims the code doesn't back (ENG-80)
- MultiplEYE and the benchmark set-up entry are held back from the beta (DATA-54)
- The app no longer discovers datasets on its own: a bundle on disk no longer puts its corpora in the picker (DATA-55)
renderno longer takes--monitor-mm,--viewing-distanceor their--compare-*twins, which nothing read (BUG-85)- CI's Coverage check measures through
sys.monitoring, taking about half as long (ENG-81) - The app draws its icons from one Material Symbols set instead of emoji (UX-138)
- The current figure, the Compare pair bundle and the animation's HTML each download in one click (UX-150)
- The current figure's PNG and SVG save instantly from the browser, and the plot's camera saves the same PNG (UX-152)
- Clicking a rail section's name flips its switch, or opens its settings where it has none (UX-153)
- Compare's B picker lists every trial, A's own included, so A and B count the same trials (CMP-22)
- A side-by-side or stacked comparison of two different texts no longer carries a caption saying so (CMP-23)
- Fixation color or Colorscale sits beside "Color fixations by", on the same row (UX-154)
- Fixation index, its label colour and its label size share one row, greyed while it is off (UX-155)
- "Snap fixations above words" moves to the bottom of 👁️ Fixations ▾ (UX-156)
- A colour range's Auto checkbox sits on the range's own row (UX-157)
- 👁️ Fixations ▾ is regrouped: one Marker group with a caption per row, a narrower title column, and more space between rows (UX-158)
- ↗️ Saccades ▾ takes the Fixations layout: one Line group, and Direction arrows as a Show row (UX-159)
- 🔥 Heatmap ▾: a Style row with Duration mass's spread beside it, and one Color group (UX-160)
- 🔵 Raw gaze ▾: one Marker group (UX-161)
- 🧹 Filter ▾: the fixation classes are one table, and All trials sits on the index range's row (UX-162)
- 📄 Stimulus ▾ and 📐 Figure & canvas ▾ take the Fixations layout (UX-163)
- The 🎬 Animate and ⚖️ Compare ▾ popovers take the same layout (UX-164)
Fixed
- The rail's "Plot controls" heading is compact again, on one line, with its new icon (BUG-88)
color_by="line"andrender --color-by linecolour each fixation by its text line, as the app's "line" option does (BUG-85)- A refused cross-screen overlay says what actually happened on each surface, and
rendernames its own flags (BUG-85) animate_scanpathco-animates one second reading, not B's whole corpus;trial_b=picks it (BUG-85)- The missing-browser hint works in the desktop app, and the in-app FAQ drops its developer-only entry (BUG-85)
- A co-animation of two datasets is held to the overlay's screen check from the API and the CLI too (CMP-21)
- A
help=tooltip shows only while its own button is hovered or keyboard-focused, and closes when the pointer leaves (BUG-86) - The ✏️ Edit dataset and add-dataset header bars take the dark theme's background instead of staying white (UX-145)
- One click on a popover's ▾ opens it; it no longer sometimes takes two or three (BUG-89)
- A stacked comparison no longer titles only its lower panel when the A/B legend is off (BUG-90)
- A rail popover no longer scrolls down into empty space below its last row (BUG-91)
- Saccade class colours and the Filter's highlight colours show their real colour in the picker, not black (BUG-92)
0.31.0 — 2026-09-24¶
Added
- A ⛶ Fullscreen control on the scanpath, animation, comparison and stimulus figures (VIZ-37)
rendercan name the two traces of a comparison —--label-a/--label-b(EXP-8)- Column auto-detection knows Tobii, SMI, Pupil Labs and Gazepoint exports, and ignores a trailing unit (DATA-25)
- Excel 97–2003
.xlsworkbooks open, instead of being refused (DATA-53) - A deployment can cap uploads for itself —
SCANPATH_MAX_UPLOAD_MB(ENG-68) - A share link carries the recording setup and Compare's per-scanpath styles (EXP-19)
renderhas a flag for every figure option, so a copied command draws the figure on screen (EXP-20)
Changed
- Streamlit 1.63 (ENG-49) — and with it a built-in select-all in every multiselect, in place of the wizard's own pair of buttons
- Streamlit 1.64 (ENG-52)
- The replay starts at ×1 — real time — instead of ×4 (VIZ-42)
- The welcome tour names the app's own icons, picks a trial before narrowing the pool, and says where you already are (UX-139)
- Animate + Compare greys the Compare settings instead of hiding them, and its Stimulus from control works there (UX-140)
- Compare warns when the two readings are of different texts, under the figure rather than inside a popover (CMP-19)
- The 🗂️ chip named "Image x" says what it is, and the chip picker uses the strip's own vocabulary (UX-141)
- The reproduce-in-code block opens with
pip install scanpath-studio(EXP-9) - The AI-assistance note asks for an issue, and stops asking for a Session JSON backup (ENG-45)
- The manuscript's MultiplEYE figure no longer hides the Illustration label to work around BUG-47 (BUG-47)
- The ℹ️ About a dataset blurbs are shorter, and OneStop's 330 texts is explained correctly (BUG-43)
- The recovery cache follows the server's bind address, not the URL the browser reports (ENG-56)
requirements.txtis gone —pyproject.tomlis the only dependency manifest (ENG-65)- Local folder access is off by default on a server other machines can reach (ENG-66)
- The
other_vis/scratch folder is gone (ENG-67) - The repo sheds its strays, ignores what must never be committed, and the edit hook lints with the pinned ruff (ENG-69)
- The hosted demo installs the app, not its test tools (ENG-71)
- The dependency floors are the versions CI tests (ENG-72)
- CI runs current actions and tool pins (ENG-73)
- A
SECURITY.mdsays how to report a vulnerability privately (ENG-74) - Words keep the interest areas the experiment defined (BUG-83)
- A default figure's colour ranges are scaled to its own trial, as the API and
renderdraw them (VIZ-46) - The docs match the app they describe (ENG-70)
Fixed
- The figure draws offline (ENG-64)
- Groups → Effect size + test compares readers, not pooled words (BUG-82)
- A zipped Parquet, Feather or Excel file uploads again (BUG-84)
- The setup wizard reads an upload's columns once, not on every click (PERF-14)
- The docs and in-app hints name the controls the app actually has (ENG-58)
- The docs site publishes even when a test is flaky, and a release refuses a tag that doesn't match the version (ENG-62)
- The package credits and licenses the bundled OneStop demo, and builds without deprecation warnings (ENG-61)
- An installed copy downloads corpora to a per-user data folder, not into
site-packages(ENG-59) - The LaCC lab OneStop option no longer prefills one maintainer's personal OneDrive path (ENG-60)
- An animated cross-screen comparison no longer says it is shown side by side while showing one scanpath (UX-144)
- A click with 🎬 Animate on no longer rebuilds the whole replay (PERF-13)
- A broken mapping or an empty filter no longer leaves a stuck "Loading…" and no way to switch dataset (BUG-81)
- The selected trial survives a visit to Corpus Analysis or the 🗂️ Data page (BUG-80)
- The 🗂️ Data page no longer crashes on MultiplEYE (BUG-79)
- Corpus Analysis computes the reading measures when the data doesn't ship them (BUG-78)
- Computing the reading measures is ~6× faster, and no longer quadratic in corpus size (PERF-12)
- The normalized corpus is no longer re-hashed on every rerun (PERF-10)
- Raw gaze is no longer re-read and re-normalized on every rerun (PERF-11)
- An uploaded table can no longer make a shared deployment read a file off its own disk (ENG-57)
- Three tutorial steps pointed at nothing: two never opened 📤 Export, one outlined a control that isn't on the Data page (BUG-77)
- Corpus Analysis computes only the subtab you have open (PERF-9)
- Regression-path (go-past) time counts the fixations of a regression that lands on a skipped word (BUG-61)
- A word first reached by a regression counts as skipped (BUG-62)
- A word nobody fixated no longer counts as a 0 ms fixation in every mean (BUG-63)
- Regression-out is a first-pass event, as in EyeLink's reports (BUG-64)
- The centred landing distance is measured from the word's actual centre (BUG-65)
- A fixation off the text ends a word's first run (BUG-66)
- Words per minute counts every screen's words on multi-page trials (BUG-67)
- The mean forward saccade leaves out return sweeps (BUG-68)
- The computation register says what the code computes (VAL-10)
- The preview launcher runs the project's own Streamlit, not whatever is on
PATH(ENG-51) - A
help=tooltip left open by a rerun now closes on its own, not only on the next pointer move (BUG-51) - The plot rail's sub-headings no longer sit on the switch below them (UX-142)
- A comparison you labelled by hand reproduces under your labels, not the builder's (EXP-8)
api.figure_codewrites the caveats it used to throw away (EXP-8)- A drift-corrected figure no longer parks one float per fixation in session state, every rerun (EXP-8)
- A stimulus-image folder no longer costs two syscalls per row of the corpus, on every rerun (PERF-8)
{trial_id}.pngresolved to7.0.pngon a table that happened to hold a float column (PERF-8)- The derived canvas is no longer re-estimated from the whole corpus on every rerun (PERF-8)
- An untouched fixation-index slider no longer copies the trial's fixations (PERF-8)
- The selected trial's
combosrow is masked once per rerun, and only when something asks (PERF-7) - A GIF export no longer holds every decoded frame in memory, and one too large for the server is refused up front (BUG-74)
- A hand-edited share link can no longer crash the app through raw gaze's sliders or a colour (BUG-69)
- A malformed recovery cache no longer crashes the app on every launch (BUG-71)
- Raw gaze's colour, size and opacity controls change the figure, and survive a save and a restart (VIZ-43)
- A share link carries the fixation flags, the replay speed, the stimulus image, Show full monitor, the colour bars and the span border (EXP-18)
- The saved config keeps the raw-gaze switch, the replay speed, the A/B legend and the blink flag; the recovery cache keeps Compare (BUG-72)
- Restoring an annotations-only backup no longer resets the view settings (BUG-73)
- A share link or saved config can no longer put a clickable link in the recipient's figure (BUG-75)
- A word spelled "None", "NA" or "null" no longer makes the dataset impossible to add (BUG-53)
- A decimal-comma export reads as numbers, and a numeric column that doesn't parse is named instead of silently filled (BUG-54)
- An Excel-named text export, a Windows-encoded CSV or an empty file no longer crashes the upload (BUG-55)
- A blank row no longer makes a dataset impossible to add (BUG-56)
- A second reading of a text gets that text's word boxes when the word table is keyed by text alone (BUG-57)
- A Trial ID picked by hand is the one used, even when the table also has a
unique_trial_idcolumn (BUG-58) - A zero-padded id (
007) matches across tables that read it differently (BUG-59) - Vendor time columns in seconds, microseconds or nanoseconds are read in milliseconds, and screen-fraction positions are flagged (DATA-40)
- A
;-separated CSV and a tab-separated.txtread as tables, not as one column (DATA-41) - A blank row in a metadata table no longer becomes a reader, trial or text named "nan" (BUG-60)
render --sampleandload_sample_data()no longer open with a warning about word ids (BUG-76)- The loading guides describe the add-dataset wizard as it is: three parts, each table mapped in its own row (DATA-42)
- A words table that joins to none of the fixations is said, on every page, and a column mapping no longer carries over to another dataset with the same headers (BUG-32)
render --animatehonours every styling flag the replay can draw (EXP-10)render --compare-withhonours--fix-index-range(EXP-11)- A palette choice no longer reproduces as a command that colours saccades by type (EXP-12)
- Bad CLI input is a message, not a traceback — and
--word-schema/--fix-schemamap unrecognised columns (EXP-13) - The documented examples run as written (ENG-53)
api.figure_code()'s defaults write a recipe that runs, with both flavours drawing the same figure (EXP-14)- An export bundle no longer holds two
aggregate/all_fixationsfiles (EXP-15) - A headless comparison draws the app's default marker opacity (CMP-20)
analyzewrites a readablecleaning_qa.csv, andcorpus --kind differencerefuses a table with nodiff(EXP-16)- A
color_by/highlight_columnnaming a missing column raises, instead of drawing a flat figure (EXP-17) - The package root exports what the docs list, and a mistyped command says so (ENG-54)
scanpath-studiolistens on this computer only unless told otherwise (ENG-55)
0.30.1 — 2026-08-28¶
Changed
- "What's in this dataset" opens with one sentence, not a full ℹ️ About section (UX-137)
- The per-metric spread reads as metrics beside the counts it explains, not a four-column table (UX-137)
render_progress/continue_buttonare gone fromwizard_shell(ENG-37)
Fixed
- A corpus' declared stimulus typeface no longer outlives the corpus (BUG-50)
- A share link that withholds the trial withholds the fixation window with it (VIZ-40)
- Four prompts still named the ✏️ Edit dataset headings UX-135 renamed (UX-135)
- The PRE-22 gate assertion could no longer fail (ENG-37)
- The Docs workflow is green again — the coverage floor sat above the real number (ENG-37)
0.30.0 — 2026-08-28¶
Added
- Save the plot settings you like as your own design presets (VIZ-39)
- Every dataset arrives with its figures already in the table (DATA-36)
- The trial metadata table reaches the CLI, the API and the export bundle (DATA-29)
- Trial metadata attaches in the add-dataset wizard too (DATA-29)
- A dataset added with only one of its two main tables can gain the other (UX-104)
- Every first-visit walkthrough — the welcome tour, each tutorial, and the dataset-setup guide — has a persistent "don't show again" (UX-110)
- Scanpath B gets its own Screen navigator in Compare mode, directly under its own row (UX-112)
- The manuscript's interface figures are scripted too (
paper/paper_ui_screenshots.py) - The app writes the Python or CLI code that rebuilds the figure you are looking at (EXP-7)
- An "AOI block" field keeps a screen's answer sub-blocks from merging into one box (UX-113)
- Text metadata — a third keyed table, joined on text id, with the same reach participant/trial metadata already have (DATA-TBD)
- The 📄 Stimulus section gets a master switch, like Fixations/Saccades — off hides the text, bounding boxes and stimulus image together (UX-128)
- A share link can carry the fixation-index window (VIZ-40)
Changed
- The README and the docs site describe the app as it is at 0.29.0
- The code no longer calls anything "the sidebar" (ENG-44)
- The plot rail's sections show no hover text (UX-103)
- ✏️ Edit dataset and ➕ Add dataset draw the same field grid (UX-104)
- Character-AOI aggregation sits with the AOI table's own fields (UX-104)
- Both metadata tables report their join in one line (UX-105)
- ✏️ Edit dataset is the add-dataset screen, end to end (UX-106)
- ✅ Save changes returns to the dataset list; ✕ Cancel only asks when there is something to lose (UX-107)
- The add-dataset wizard's five stages read as one flat, numbered sequence, and every stage-2 table title gets the mapping fields' own hover format (UX-113)
- "Derive columns from the filename" can derive from any uploaded column, not just the filename (UX-113)
- A saved wizard setup also restores the filename-derive and keep/filter choices (UX-113)
- The three metadata tables' uploads sit in one row, each with a clearer "✕ Detach", a match-count caption, and a required marker on the id field (DATA-TBD)
- The wizard's "Keep extra fields" stage folded into "Map data fields" — one per-table picker with Select all/None, the same picker added to each metadata table, and small "N identified · M joined" / "P field(s) kept" captions replacing the old banner (UX-114)
- Removing a metadata table's file from its own uploader now detaches the table, matching every other upload (UX-115)
- A metadata table's join runs once, when ✅ Add dataset is clicked — the wizard no longer shows a live join report while the dataset is still being set up (UX-116)
- The "rows disagree" warning on a metadata table now says "duplicate ids" and that the rows are dropped (UX-114)
- The wizard offers no "Dataset format" choice this release — every upload goes through Generic (UX-114)
- A wizard table upload's row/column count is a small caption, and its preview is a click-away popover instead of a permanent table (UX-117)
- The "Extra fields to keep" picker and the "Aggregate character AOIs" toggle line up under the mapping pickers above them, not under the row's own name label; the preview popover moved to the left, before the row/column count (UX-118)
- The upload preview button is icon-only and packed directly against the row/column count, no gap (UX-119)
- The raw gaze table gets an "Extra fields to keep" picker too, matching Fixations and Words/IA (UX-120)
- A keep-picker chip reads as just the field name — the "· meta"/"· extra" category suffix is gone (UX-121)
- Fixations/Words/Raw gaze upload directly in "Map data fields," in their own row-name column, instead of a separate "Upload data files" step (UX-122)
- "Derive columns from the filename" stays first, short row-name titles fit (UX-123)
- The "5GB per file" note moves to the title's hover, and the uploaded-file chip fits the narrow row-name column without cropping (UX-124)
- The upload row-name column actually centers vertically now, against the whole table's fields — not just row 1's (UX-125)
- The Data page's raw-data section always shows exactly six tables — fixations, AOIs, raw gaze, participants, trials, texts — and the computed analysis tables (including the reconstructed stimuli list) are held back this release (UX-126)
- The participant/trial/text metadata tables upload and map in the same row format as Fixations/AOI/Raw gaze — one row per table, under a small "Metadata" heading in "Map data fields"; "Upload data tables" (stage 2) is now just the intro note and the restore-a-setup control, and every row's upload column is wider so the Browse-files button fits (UX-127)
- "Map data fields" folded into "Upload data tables" — the add-dataset wizard is two numbered stages, not three; the metadata tables lost their redundant ✕ Detach and 🔎 Join details, and every row's stats/keep-count now match Fixations/AOI/Raw gaze's own shape (UX-129)
- The "upload at least one table" nudge repeats above "Derive columns from the filename," not just at the top of the stage (UX-129)
- "Derive columns from the filename" picks a table before a column, and gets a ➕ Another button for several lines — one shared Apply, each split named
<column>_<n>so a second split never overwrites the first's columns (UX-129) - The dataset table's Edit/Rename/Remove/About actions are icon-only and narrower, and the open dataset's ℹ️ About prose now also shows inline under the table, above "What's in the dataset" (DATA-TBD)
- 💾 Session's 🗄️ Automatic recovery says which datasets it counts, and its fine print moved behind one ❔ popover (UX-130)
- The computed trial stats are chips on the strip, not a Summary stats popover beside it (UX-131)
- 📂 Available datasets leads with Kind, names its four action columns, marks Counts as explained, and moves the corpus home link into ℹ️ About (UX-132)
- "What's in this dataset" is one tab bar — 📊 Stats first, then the six raw tables, none of them behind an expander (UX-133)
- ℹ️ About and the FAQ are shorter: no affiliations, licence tag, privacy/tutorial links or methodology button, and "Where does my data go?" describes the recovery cache (UX-134)
- The Trial ID verdict is raised as a modal when a dataset is added or its mapping saved, instead of a page-wide banner that never went away (VAL-9)
- ✏️ Edit dataset ends with the add screen's own ⬇️ Save setup · ✅ Save changes pair (UX-106)
- ✏️ Edit dataset's sections are the add screen's numbered parts, in the add screen's order (UX-135)
Fixed
- OneStop reads as 330 texts, not 7 (BUG-43)
- Two tour steps point at the trial filters again, instead of at nothing (UX-101)
- A trial is counted as a reader-and-trial pair, not a bare trial id (DATA-36)
- Four prompts describe the trial filters as they are since UX-64 (UX-38, UX-64)
- A large release no longer fails to create its GitHub release (ENG-43)
- The plot rail's section titles no longer grow a scrollbar (UX-102)
- A chip field whose recorded column is empty no longer hides the metadata value (DATA-29)
- A metadata table that joins to nothing says so (DATA-20, DATA-29)
- Detaching the trial metadata table now detaches it (DATA-29)
- A rejected mapping on ✏️ Edit dataset says why instead of doing nothing (UX-106)
- A trial id spelled
"101"in one table and"101.0"in another now joins (BUG-44) - ➕ Add dataset's mapping pickers offer every column in the file, not just the ones a previous upload kept (UX-108)
- A screen_id column with a blank cell no longer rejects the whole mapping (BUG-45)
- Comparing two trials no longer truncates the longer one's fixations to the shorter one's count (BUG-46)
- Every screen after the first of a multipart trial no longer claims to be an Illustration (BUG-47)
- The Hover fields controls apply while Compare mode is on (CMP-17)
- B's own ◀ ▶ no longer silently undoes its step while linked to A (CMP-18)
- The trial-position sliders' fill matches their thumb on a right-to-left browser (UX-109)
- The multipart Screen picker aligns under Select Trial, and its slider matches the trial slider's width (UX-111)
- Character-AOI aggregation no longer merges same-numbered words across different screens of the same trial (UX-113)
- The app never grows a page-wide horizontal scrollbar (UX-129)
- A wizard table's title no longer crowds the top of its row when nothing is uploaded yet (UX-129)
- ℹ️ About/Rename/Remove no longer reopen on every rerun and block whichever dialog you actually clicked next, once dismissed by ✕ or Escape (DATA-TBD)
- A
help=tooltip no longer stays open after the pointer leaves — or swallows the click aimed at what it covers (BUG-48) - ✏️ Edit dataset's ✕ Cancel confirmation acted on the wrong button: ✕ Leave did nothing and Keep editing left (BUG-49)
rendergains the three settings only the Python form could express —--fix-index-range,--highlight-column/--critical-span-style, and--fixation-flag(EXP-8)- An untouched figure's reproduction code writes no settings at all, instead of two dicts nobody set and a false "no
renderflag" warning (EXP-8) - A
--highlight-column ''in the printed CLI command is quoted, so it means "highlight nothing" rather than eating the next flag (EXP-8) - "Recovered your last session" only appears when something you would recognise actually came back (UX-136)
0.29.0 — 2026-08-21¶
Added
- Rebuilt data-upload wizard with an honest experimental setup (DATA-22)
- Compare scanpaths across datasets (CMP-8)
- Overlay two datasets recorded on the same screen (CMP-11)
- Compare mode on the CLI and the Python API (CMP-9)
- Multipart trials (DATA-21)
- Use-case-specific tutorials (UX-40)
- Author scanpaths directly on the stimulus canvas (VIZ-33)
- Optional monitor-pixel coordinate grid (VIZ-34)
- Honest progress for every app export path (EXP-6)
- An easter egg in the header (UX-39)
- Rename a dataset after you have added it (DATA-23)
- Step both compared trials at once (CMP-13)
- Narrow the trial pool by a numeric range (UX-49)
- A warning when one trial id covers more than one reading (VAL-7)
- MultiplEYE: every screen of a trial, including the comprehension questions (DATA-24)
- Attach a table of participant metadata (DATA-20)
- A computation register and a methodology page (VAL-5)
- Thirty-one harmonised public reading corpora, each its own data source (DATA-27)
- Word-box geometry recovered in four tiers, and labelled with which one you got (DATA-27)
- Every public corpus on a share link (DATA-27)
- Harmonised corpora on the CLI and in the Python API (DATA-27)
- Harmonised corpora are marked (WIP) in the picker (DATA-27)
- Raw gaze gets its own colour, marker size and opacity controls (UX-86)
- Each tutorial can be told not to auto-show, on its own (UX-85)
- A
{dataset_name}placeholder for plot titles and captions (VIZ-36) - Attach a table of trial information (DATA-29)
- A benchmark that times every computation in the register at corpus scale (PERF-4)
- OneStop's real word boxes are recovered from its raw EyeLink export (DATA-30)
- Recorded fixation y is retained when it shares the word boxes' real coordinate frame (DATA-31)
Fixed
- The dataset table's counts were the first dataset's, for every row (DATA-32)
- Trial-level source merging no longer emits pandas' empty-concatenation FutureWarning
- MultiplEYE no longer crashes when a cached word table has stale screen order — the screen picker uses the fixation-onset order, which is authoritative for each reader, while other multipart metadata conflicts still fail loudly.
- The Session page's panels no longer render on every view (BUG-34)
- A word's label sits centred in its box, not jammed against the left edge (BUG-30)
- Leaving the add-dataset wizard asks first, instead of switching to the half-built dataset (BUG-31)
- A greyed-out control in the plot rail no longer prints raw HTML beside its name (UX-68)
- The tracker starts on Windows, so its Save button works there (BUG-33)
- The tracker server reads its own files as UTF-8, not as the machine's locale codec (ENG-40)
- The tracker no longer silently overwrites edits made outside the page (ENG-41)
- Claiming a task created in the UI no longer breaks every later tracker save (ENG-42)
- A harmonised corpus loads by its published schema, not by guesswork (DATA-27)
- Wizard steps no longer collapse while you are editing them (DATA-22, supersedes DATA-19)
- The upload-size warning no longer appears when you run locally (DATA-22)
- Uploads are no longer capped at 200 MB outside the repo root (DATA-22)
- Adding a dataset no longer hangs after "Dataset added" (PRE-6)
- A public corpus now reports its declared monitor, not its data extents (CMP-11)
- Switching to a stored upload no longer keeps the previous source's monitor (CMP-8)
- Quick-view and full-monitor transitions keep the current visualization state (BUG-20, BUG-21)
- Participant narrowing keeps eligible trial-sort fields (BUG-22)
- Trial selection is always the same picker — selectbox, slider and ◀ ▶ arrows (BUG-23)
- Corpus Analysis no longer pools a multipart trial's screens into one word axis (BUG-26)
- Switching datasets no longer keeps the previous one's column mapping (DATA-24)
- Reset settings is visible in the plot rail without zooming out (BUG-24)
- Saccade amplitude is pixels everywhere, and EyeLink's degrees keep their own columns (BUG-25)
- A letter is one character advance wide, not one character plus an inter-word space (BUG-27)
- A Data-page tutorial step no longer offers to open the page you are on (UX-40)
- Starting a tutorial from the chooser now actually starts it (UX-40)
- The "Loading…" banner no longer lingers above a finished page (DATA-26)
- A column mapping the pipeline rejects no longer takes the whole app down (BUG-28)
- The AOI picker's participant/text counts were never shown, only the Fixations one's (BUG-35)
- Confirmation dialogs' buttons did nothing, and two destructive buttons had no confirmation at all (BUG-36)
- Plot-rail popovers now open reliably on the first click (BUG-37)
- A spreadsheet upload no longer fails on a dependency the package never declared (BUG-41)
- Clearing the recovery cache no longer fails when a file cannot be deleted (BUG-42)
- Three computation-log tests no longer fail on Streamlit's bare-mode warnings (BUG-39)
Changed
- A dataset loads about three times faster, in a seventh of the memory (PERF-6)
- Every interaction on a big corpus stops paying most of a ~1.2 s copying tax (PERF-6)
- The trial-identity check screens a sample, with a button for the full census (PERF-6)
- A long export says how fast it is going and when it will finish (PERF-6)
- Work is tracked in GitHub Issues; the in-repo tracker is now a read-only archive (ENG-32)
- 💾 Session is a dialog opened from the nav, not a top-level page (UX-100)
- The Data page is two screens: 📂 Available datasets and ✏️ Edit dataset (DATA-35)
- Clearer wording and a wider page: trial-picker help, Summary stats, page gutters, Automatic recovery (UX-99)
- Data, annotations, comparisons, export and plot controls are more compact and consistent (UX-94)
- Session is organized around recovery, JSON backup, reset and debug tools (UX-96)
- 🔥 Overlays is gone — Heatmap and Raw gaze are their own plot-rail sections (UX-86)
- Compare mode puts both control lines above both chip strips (CMP-16)
- Deleting a dataset drops the computations derived from it (UX-87)
- The add-dataset page drops Screen name and every "still to do" badge (UX-88)
- The column mapping groups its rows by table, not by kind (UX-89)
- The add-dataset page objects only once you press Add, and then everywhere at once (UX-90)
- Every required field really does go red on a failed add, including Trial ID (UX-91)
- Confirming an auto-detected column clears its amber mark (UX-92)
- The wizard's two footer buttons are one matched pair (UX-93)
- The add-dataset wizard's two Help buttons are one ❓ Help popover (UX-84)
- The add-dataset wizard's mapping merges each table's identity and geometry fields into one two-line block (UX-55)
- Every plot-rail section is a toggle and a ▾ on one line, with its controls in a popover (UX-80)
- Stimulus typography moves beside the text it draws; the screen's physical geometry leaves the rail (UX-81)
- A zipped table may be up to 32 GB, and the caps are yours to move (DATA-34)
- A layer's settings stay readable while the layer is switched off (UX-97)
- Streamlit is upgraded to 1.62, with native no-wrap rows and viewport-bounded popovers (ENG-43)
- The dataset list is Available datasets: click a name to open, and the open row is tinted (UX-77, UX-78)
- Each dataset's counts are computed once and remembered (DATA-32)
- Deleting a dataset and leaving the wizard both ask in a modal (UX-79)
- Preprocessing is held back from the app until the next release (PRE-22)
- One 🧹 Filter section for the whole figure, replacing the per-layer filters (UX-72)
- Reset visualization is a button, not a popover holding one (UX-73)
- Each reading's title and chips are one line, under that reading's control line (UX-75)
- One control line on the Scanpath view — and the same line for compare's second trial (UX-64)
- A cross-dataset comparison names both corpora, not just the second (CMP-15)
- The datasets are a sortable table; editing a mapping looks like adding one, and deleting asks first (UX-54)
- Help is a menu that opens the tutorial, the FAQ and About over your work (UX-65)
- The add-dataset screen keeps one title row on screen while you scroll (UX-66)
- A mapping dropdown opens wide enough to read the whole column name (UX-71, UX-57)
- Each count sits under the field it counts, in small text (UX-67)
- Fixation fields run over two lines, and the AOI fields group together (UX-55)
- One menu: Scanpath, Corpus Analysis, Data, Session, Help (UX-63)
- The app's name is a wordmark in the header, not a heading on the page (UX-62)
- Animate and Compare are split buttons: the toggle, and a ▾ for its settings (UX-68)
- The Scanpath subtabs read
label | field, so a panel fits on one screen (UX-69) - The Word box and Recording setup match the rest of the wizard (UX-57, UX-58)
- Every wizard field's title sits above its control (UX-53)
- A mapping dropdown never truncates a column name (UX-53)
- One picker per table, and a whole theme on one row (UX-53)
- Choosing a mapping approves it; clearing one drops the suggestion (UX-53)
- A field's clear button is Streamlit's own, inside the select (UX-53)
- The wizard is two linear parts, with the dataset name above both (UX-53)
- Mapping fields share a row instead of one per line (UX-53)
- The setup page drops its summary card, and raw gaze joins the other tables (UX-53)
- The wizard is one part, and Advanced is gone (UX-53)
- Explanatory text on the setup page is hover-only (UX-53)
- The upload wizard is two parts, not seven steps (UX-53)
- A mapping row shows its state as a tint on the select, not a sentence beside it (UX-53)
- The Data page is denser, and its smallest text is legible (UX-53)
- One figure-settings contract now powers every renderer (ENG-28)
- Core module ownership is one-way (ENG-28)
- The visualization rail scrolls independently and packs controls more tightly (UX-43, UX-44)
- Every control row above the plot shares one column grid (UX-47)
- Figure & canvas is grouped instead of one long list (UX-48)
- A layer section's own toggle reads "Visible" (UX-50)
- One Data page — the source, the column mapping, the tables and preprocessing in one place (DATA-26)
- The menu bar is down to Help and Session (UX-38)
- Rail controls read as a form: label beside the field, not above it (UX-51)
- The sidebar is gone — a top menu bar and Streamlit's native top navigation replace it (UX-38)
- Nothing is hidden behind a URL param any more — debug mode and the ground-truth trial are both in the UI (UX-37)
- Drift correction and NLD similarity are not exposed in this build (PRE-21)
- The Data page has one heading level and folds its tables away (UX-52)
- The column mapping reads as a form, and hides what only multipart data needs (UX-52)
- The tutorials follow the app as it is today, and "explore a corpus" answers a real question (UX-40)
- The mapping's "auto-detected" note sits beside its field, and trial identity is its own section (UX-52)
- Participant metadata is a step of the upload wizard, groups a cohort, and can be trimmed on the way out (DATA-20)
- Tracker write-ups are structured fields, not bold-led prose (ENG-38)
- One-click status moves, and one home for everything waiting on you (ENG-38)
- CONTRIBUTING covers joining an in-flight project, not just opening a PR (ENG-39)
- The tracker records who has an item, and state.json stops conflicting on every pull (ENG-39)
- Three more corpora reconstruct their published screen, and one stops claiming a screen it never had (DATA-27)
- Zooming the plot magnifies it, instead of stretching the axes (VIZ-38)
- The trial control line reads as a form: a funnel, a titled sort row, and a dataset tooltip that is about datasets (UX-98)
0.28.0 — 2026-08-08¶
Added
- Reset settings (UX-26)
- Draw only some saccade types (VIZ-31)
- Title & caption on the figure, live on the rail (EXP-5)
- Per-chip colour highlight, for any dataset (UX-28)
- Editable Compare A/B legend labels (UX-31)
- Pick which columns "Stimulus & questions" shows (UX-32)
- FAQ: "I edited the code and nothing changed?" (UX-35)
- A coverage badge, a published coverage report, and a CI floor (ENG-37)
Fixed
- Reruns are ~28× faster, and per-trial subtabs load on demand (PERF-3)
- Author a scanpath: edits stick, and "Target word" is explained (BUG-19)
- The compare-mode heatmap now actually draws (CMP-7)
- Changing a rail setting mid-rerun no longer crashes the app (BUG-18)
- Rail labels no longer break mid-word (
styles.py) - Stimulus & questions no longer shows bogus Q&A fields on generic uploads (UX-32)
Changed
- The data-source picker moved into the main view (UX-25)
- The visualization rail is grouped instead of flat (VIZ-31)
- Canvas, font and background controls moved to the rail (VIZ-31)
- Colourblind-safe is now the default palette (VIZ-32)
- The three control rows above the plot read as one cluster (UX-27)
- Title & caption moved into a popover, and the
{field}vocabulary is documented (EXP-5, UX-31) - Trial picker reads "Select Trial" (UX-33)
- About panel's AI-assistance note trimmed to match the docs (UX-36)
- View modes: neutral section icon, and the Playback popover tightened further (UX-30)
- Quick-view labels fall back to emoji-only when the rail is narrow (UX-29)
- Welcome tour: copy, step order and highlighting polish (UX-34)
- CHANGELOG entries: a Slack-pasteable headline, details below (ENG-34)
- Runtime moved to Streamlit 1.61.1 (ENG-31)
- Widget values now persist natively instead of by hand (ENG-36)
- Raw data tables scroll instead of paginating (ENG-36)
- Open a trial straight from a Corpus Analysis table (ENG-36)
Removed
- Five helpers nothing referenced (ENG-28)