fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(guided-study): stepper taps no longer bounce back to initialStep - #1654

Merged
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back
Apr 24, 2026
Merged

fix(guided-study): stepper taps no longer bounce back to initialStep#1654
CraigBuckmaster merged 1 commit into
masterfrom
fix/guided-study-stepper-bounce-back

Conversation

@CraigBuckmaster

Copy link
Copy Markdown
Owner

Problem

On any chapter where the user had previously advanced the guided study past the scene step, opening the study session from ChapterScreen's StudySessionCTA makes every stepper tap appear inert. Tapping Scene / Observe / Explore / Synthesize does nothing visible — the highlighted pill snaps right back to whatever step the route was opened with.

Reproduced on the iOS simulator (rentamac) during post-merge review of #1602.

Root cause

StudySessionScreen.tsx had a naive sync effect:

useEffect(()=>{if(initialStep&&sessionId!=null&&currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

With currentStep in the dependency array and no one-shot guard, the effect re-fires every time currentStep changes:

  1. User taps "Observe" → setCurrentStep('observe')currentStep is 'observe'
  2. Effect re-runs on the change → sees currentStep !== initialStep ('review') → calls setSessionStep('review')
  3. State snaps back to 'review' within one render cycle

Taps were registering fine; they just got reverted instantly. Explains why one user reported "I can't tap any of the other options above (Scene, observe, explore, etc)" — they could tap, they just couldn't stay.

Fix

Guard the sync with a useRef keyed on sessionId. The sync runs exactly once per session load (when sessionId first becomes non-null) and short-circuits on every subsequent re-run:

constappliedInitialStepForSessionRef=useRef<number|null>(null);useEffect(()=>{if(sessionId==null||!initialStep)return;if(appliedInitialStepForSessionRef.current===sessionId)return;appliedInitialStepForSessionRef.current=sessionId;if(currentStep!==initialStep){voidsetSessionStep(initialStep);}},[currentStep,initialStep,sessionId,setSessionStep]);

The ref is keyed on sessionId (not just a boolean) so that if the user navigates to a different chapter without unmounting — chapter?.id changes, the hook loads a new session, sessionId updates — the new initialStep still applies for the new session. For a stable session, re-renders that carry the same sessionId no-op.

What this does NOT change

  • goStep still awaits savePromptDrafts() and saveSynthesis() before calling setCurrentStep. The draft-persistence side effects on step change are preserved.
  • No change to useGuidedStudySession hook. The persistence semantics are unchanged.
  • No change to ChapterScreen's StudySessionCTA route props. The initialStep param still flows through and is still honored on first load.

Out of scope (follow-ups flagged separately)

The review session also surfaced a cosmetic concern in StudyModeSelector — the active-state background is ${base.gold}16 (~9% opacity gold tint), which in the sepia theme is barely distinguishable from base.bgSurface. Taps there are functionally working (pure React state), but the visual affordance is weak. Captured in #1652 under "also observed" for the Guided Study re-plan; not fixed here.

Test plan

  • npx tsc --noEmit — expected to pass (only added useRef to an existing React import).
  • npx jest — existing tests should pass; no screen-level test exists for StudySessionScreen (pattern: MyStudyScreen.test.tsx also absent). Hook tests in __tests__/hooks/useGuidedStudySession.test.ts are unaffected since the fix lives in the consuming screen.
  • Manual (rentamac iOS simulator):
    1. Open Genesis 1, advance the guided study past "Scene" (tap "Begin observing" or use the stepper), back out to the chapter.
    2. Tap Continue Study from the CTA.
    3. Screen should arrive on the step you left (as before).
    4. Tap any other stepper pill — the UI should switch to that phase and stay.
    5. Tap back through several pills to confirm no bounce.

Rollback

Revert the single commit. Reverts behavior to the existing bounce-back bug — no schema, no persistence, no store changes to unwind.

Discovered during rentamac review on April 24 2026. Companion to #1653 (Canon Comparison tab-stretch fix) and #1652 (Guided Study re-plan capture card).

Root cause: the useEffect that syncs `initialStep` route param into
the session had `currentStep` in its dependency array and no
one-shot guard. Every tap on a stepper pill triggered:
1. setSessionStep(<new step>) → currentStep updates
2. Effect re-runs on currentStep change
3. currentStep !== initialStep → effect snaps back to initialStep
Visible symptom: on any chapter where the user had previously moved
past 'scene', ChapterScreen's StudySessionCTA passes the stored
current_step as `initialStep`, the user arrives on that step, and
taps on every other stepper pill appear to do nothing (they briefly
flip and immediately revert within one render cycle).
Fix: guard the sync with a ref keyed on sessionId so initialStep is
applied exactly once per session load. Subsequent stepper taps leave
the ref untouched and the effect short-circuits, letting setCurrentStep
persist normally.
When sessionId changes (different chapter, new session), the ref's
stored id no longer matches so initialStep syncs again for the new
session — behavior we want.
@github-actions

Copy link
Copy Markdown

Test Results

✅ All tests passed

PassedFailedTotal
Tests✅ 3617❌ 03617
Suites✅ 491❌ 0491

Coverage

StatementsBranchesFunctionsLines

⏱️ Duration: 70.6s

@CraigBuckmaster
CraigBuckmaster merged commit f3a54d9 into masterApr 24, 2026
6 checks passed
@CraigBuckmaster
CraigBuckmaster deleted the fix/guided-study-stepper-bounce-back branch April 24, 2026 18:52
Sign up for freeto 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.

2 participants

@CraigBuckmaster@claude