@@ -13,6 +13,8 @@ import simulateTwoTrials from './src/probability-lab/engine/simulate-two.js';
1313import { createRunner } from './src/probability-lab/engine/runner.js' ;
1414import render from './src/probability-lab/ui/render.js' ;
1515import { createStore } from './src/probability-lab/state/store.js' ;
16+ import { IndexHistory , PackedPairHistory } from './src/probability-lab/state/trial-history.js' ;
17+ import { createHistoryView } from './src/probability-lab/ui/history-view.js' ;
1618import NumericSlider from './design-system/components/numeric-slider/numeric-slider.js' ;
1719
1820function $ ( id ) {
@@ -58,6 +60,15 @@ const els = {
5860 deviceView : $ ( 'pl-device-view' ) ,
5961 trials : $ ( 'pl-trials' ) ,
6062 last : $ ( 'pl-last' ) ,
63+ historyButton : $ ( 'pl-history' ) ,
64+ historyModal : $ ( 'pl-history-modal' ) ,
65+ historySummary : $ ( 'pl-history-summary' ) ,
66+ historyEmpty : $ ( 'pl-history-empty' ) ,
67+ historyScroller : $ ( 'pl-history-scroller' ) ,
68+ historyViewport : $ ( 'pl-history-viewport' ) ,
69+ historyItems : $ ( 'pl-history-items' ) ,
70+ historyJumpTop : $ ( 'pl-history-jump-top' ) ,
71+ historyJumpLatest : $ ( 'pl-history-jump-latest' ) ,
6172
6273 barChart : $ ( 'pl-bar-chart' ) ,
6374 lineChart : $ ( 'pl-line-chart' ) ,
@@ -87,6 +98,8 @@ const sliderInstances = {
8798// Store speed slider instance
8899let speedSlider = null ;
89100let settingsModal = null ;
101+ let historyModal = null ;
102+ let historyView = null ;
90103
91104/**
92105 * Generates a random numeric seed string for the RNG
@@ -137,6 +150,7 @@ const store = createStore({
137150 counts : [ ] ,
138151 lastIndex : null ,
139152 history : [ ] ,
153+ trialHistory : new IndexHistory ( ) ,
140154 eventSelected : new Set ( ) ,
141155 } ,
142156
@@ -165,6 +179,7 @@ const store = createStore({
165179 lastA : null ,
166180 lastB : null ,
167181 selectedCell : null ,
182+ trialHistory : new PackedPairHistory ( ) ,
168183 } ,
169184} ) ;
170185
@@ -176,6 +191,9 @@ const runner = createRunner(store.getState().running, {
176191 const state = store . getState ( ) ;
177192 render ( els , state ) ;
178193 applyVisibility ( state ) ;
194+ if ( historyModal ?. isOpen && historyView ) {
195+ historyView . sync ( state , { preserveScroll : true } ) ;
196+ }
179197 activityLogger . maybeLogStatus ( state ) ;
180198 } ,
181199 onDone : updateControls ,
@@ -210,6 +228,7 @@ function resetSingleSimulation() {
210228 draft . single . counts = Array ( def . labels . length ) . fill ( 0 ) ;
211229 draft . single . lastIndex = null ;
212230 draft . single . history = [ ] ;
231+ draft . single . trialHistory ?. clear ( ) ;
213232 draft . single . eventSelected = filtered ;
214233 } ) ;
215234
@@ -247,6 +266,7 @@ function resetTwoSimulation() {
247266 draft . two . lastA = null ;
248267 draft . two . lastB = null ;
249268 draft . two . selectedCell = null ;
269+ draft . two . trialHistory ?. clear ( ) ;
250270 } ) ;
251271}
252272
@@ -269,6 +289,9 @@ function resetSimulation({ reason = 'unknown', logSettings = false } = {}) {
269289 render ( els , store . getState ( ) ) ;
270290 applyVisibility ( store . getState ( ) ) ;
271291 syncUiFromState ( ) ;
292+ if ( historyModal ?. isOpen && historyView ) {
293+ historyView . sync ( store . getState ( ) , { scrollToLatest : true } ) ;
294+ }
272295
273296 const currentState = store . getState ( ) ;
274297 if ( logSettings ) {
@@ -800,6 +823,42 @@ function initSettingsModal() {
800823 }
801824}
802825
826+ function initHistoryModal ( ) {
827+ if ( historyModal ) return ;
828+ if ( ! els . historyModal ) return ;
829+
830+ const modalContent = els . historyModal ;
831+ const wasHidden = modalContent . hidden ;
832+ modalContent . hidden = true ;
833+
834+ historyModal = new Modal ( {
835+ size : 'medium' ,
836+ title : 'Trial History' ,
837+ content : modalContent ,
838+ onOpen : ( ) => {
839+ if ( historyView ) historyView . sync ( store . getState ( ) , { scrollToLatest : true } ) ;
840+ } ,
841+ } ) ;
842+
843+ if ( wasHidden ) modalContent . hidden = false ;
844+
845+ historyView = createHistoryView ( {
846+ summaryEl : els . historySummary ,
847+ emptyEl : els . historyEmpty ,
848+ scrollerEl : els . historyScroller ,
849+ viewportEl : els . historyViewport ,
850+ itemsEl : els . historyItems ,
851+ jumpTopButton : els . historyJumpTop ,
852+ jumpLatestButton : els . historyJumpLatest ,
853+ } ) ;
854+
855+ if ( els . historyButton ) {
856+ els . historyButton . addEventListener ( 'click' , ( ) => {
857+ historyModal . open ( ) ;
858+ } ) ;
859+ }
860+ }
861+
803862function syncUiFromState ( ) {
804863 const state = store . getState ( ) ;
805864 els . stepSize . value = String ( state . stepSize ) ;
@@ -887,6 +946,7 @@ function updateTwoControlsForRelationship() {
887946
888947function initEventListeners ( ) {
889948 initSettingsModal ( ) ;
949+ initHistoryModal ( ) ;
890950
891951 els . spinnerSectors . addEventListener ( 'change' , ( ) => {
892952 const clamped = clamp ( parseInt ( els . spinnerSectors . value , 10 ) , 2 , 12 ) ;
@@ -1057,6 +1117,9 @@ function initEventListeners() {
10571117 const state = store . getState ( ) ;
10581118 render ( els , state ) ;
10591119 applyVisibility ( state ) ;
1120+ if ( historyModal ?. isOpen && historyView ) {
1121+ historyView . sync ( state , { preserveScroll : true } ) ;
1122+ }
10601123 } ) ;
10611124}
10621125
0 commit comments