Conversation
…ive Tools - Added biostats_immersive.html: Complete immersive learning interface featuring three interactive states (3D Knowledge Universe with Three.js visualization, AR Glossary with flip cards, Quantum Computation dashboard with dynamic calculators), MathJax integration for LaTeX rendering, and cohesive neon-themed UI - Updated .gitignore: Refined ignore patterns with explicit code block formatting and additional compressed file extensions for better project hygiene This commit delivers a fully functional, visually rich educational tool for biostatistics concepts using modern web technologies. The system integrates 3D visualization, interactive learning aids, and real-time computation tools into a single cohesive experience.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a reorganized .gitignore file and a new interactive biostatistics learning application (biostats_immersive.html) built with Three.js and MathJax. The reviewer's feedback correctly identifies accidental markdown backticks in the .gitignore file, a lack of defensive input validation and division-by-zero guards in the calculation logic, potential performance issues from running the Three.js animation loop when the 3D view is inactive, and missing checks to ensure MathJax is fully loaded before rendering formulas.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| ``` | ||
| # Compiled and build artifacts |
There was a problem hiding this comment.
| *.tar.zst | ||
| ``` No newline at end of file |
| if(type === 'sensitivity') { | ||
| const tp = parseFloat(document.getElementById('in-tp').value); | ||
| const fn = parseFloat(document.getElementById('in-fn').value); | ||
| result = tp / (tp + fn); | ||
| label = "SENSITIVITY"; | ||
| interp = result > 0.9 ? "Excellent screening test." : "May miss significant cases."; | ||
| } else if (type === 'ppv') { | ||
| const tp = parseFloat(document.getElementById('in-tp').value); | ||
| const fp = parseFloat(document.getElementById('in-fp').value); | ||
| result = tp / (tp + fp); | ||
| label = "POSITIVE PREDICTIVE VALUE"; | ||
| interp = `Given current prevalence, a positive test means ${Math.round(result*100)}% chance of disease.`; | ||
| } else if (type === 'or') { | ||
| const a = parseFloat(document.getElementById('in-a').value); | ||
| const b = parseFloat(document.getElementById('in-b').value); | ||
| const c = parseFloat(document.getElementById('in-c').value); | ||
| const d = parseFloat(document.getElementById('in-d').value); | ||
| result = (a*d)/(b*c); | ||
| label = "ODDS RATIO"; | ||
| interp = result > 1 ? "Exposure associated with higher odds of disease." : "Exposure associated with lower odds."; | ||
| } else if (type === 'rr') { | ||
| const re = parseFloat(document.getElementById('in-re').value); | ||
| const ru = parseFloat(document.getElementById('in-ru').value); | ||
| result = re / ru; | ||
| label = "RELATIVE RISK"; | ||
| interp = result === 1 ? "No association." : `Exposed group is ${result.toFixed(2)}x more likely to develop outcome.`; | ||
| } else if (type === 'ci') { | ||
| const mean = parseFloat(document.getElementById('in-mean').value); | ||
| const sd = parseFloat(document.getElementById('in-sd').value); | ||
| const n = parseFloat(document.getElementById('in-n').value); | ||
| const z = parseFloat(document.getElementById('in-z').value); | ||
| const margin = z * (sd / Math.sqrt(n)); | ||
| result = margin; | ||
| label = "MARGIN OF ERROR (±)"; | ||
| interp = `95% CI: [${(mean-margin).toFixed(2)}, ${(mean+margin).toFixed(2)}]`; | ||
| } |
There was a problem hiding this comment.
Implement defensive input validation and division-by-zero guards in the calculation logic. Currently, empty inputs (which parse to NaN), zero values, or invalid sample sizes (e.g., N <= 0 in confidence intervals) will result in NaN or Infinity being displayed in the UI, or could cause runtime issues.
if(type === 'sensitivity') {
const tp = parseFloat(document.getElementById('in-tp').value) || 0;
const fn = parseFloat(document.getElementById('in-fn').value) || 0;
const denominator = tp + fn;
result = denominator === 0 ? 0 : tp / denominator;
label = "SENSITIVITY";
interp = result > 0.9 ? "Excellent screening test." : "May miss significant cases.";
} else if (type === 'ppv') {
const tp = parseFloat(document.getElementById('in-tp').value) || 0;
const fp = parseFloat(document.getElementById('in-fp').value) || 0;
const denominator = tp + fp;
result = denominator === 0 ? 0 : tp / denominator;
label = "POSITIVE PREDICTIVE VALUE";
interp = `Given current prevalence, a positive test means ${Math.round(result*100)}% chance of disease.`;
} else if (type === 'or') {
const a = parseFloat(document.getElementById('in-a').value) || 0;
const b = parseFloat(document.getElementById('in-b').value) || 0;
const c = parseFloat(document.getElementById('in-c').value) || 0;
const d = parseFloat(document.getElementById('in-d').value) || 0;
const denominator = b * c;
result = denominator === 0 ? 0 : (a * d) / denominator;
label = "ODDS RATIO";
interp = result > 1 ? "Exposure associated with higher odds of disease." : "Exposure associated with lower odds.";
} else if (type === 'rr') {
const re = parseFloat(document.getElementById('in-re').value) || 0;
const ru = parseFloat(document.getElementById('in-ru').value) || 0;
result = ru === 0 ? 0 : re / ru;
label = "RELATIVE RISK";
interp = result === 1 ? "No association." : `Exposed group is ${result.toFixed(2)}x more likely to develop outcome.`;
} else if (type === 'ci') {
const mean = parseFloat(document.getElementById('in-mean').value) || 0;
const sd = parseFloat(document.getElementById('in-sd').value) || 0;
const n = parseFloat(document.getElementById('in-n').value) || 0;
const z = parseFloat(document.getElementById('in-z').value) || 0;
if (n <= 0) {
result = 0;
label = "MARGIN OF ERROR (±)";
interp = "Invalid sample size (N must be greater than 0).";
} else {
const margin = z * (sd / Math.sqrt(n));
result = margin;
label = "MARGIN OF ERROR (±)";
interp = `95% CI: [${(mean-margin).toFixed(2)}, ${(mean+margin).toFixed(2)}]`;
}
}| function switchView(viewId) { | ||
| document.querySelectorAll('.view-container').forEach(el => el.classList.remove('active')); | ||
| document.querySelectorAll('.nav-controls button').forEach(el => el.classList.remove('active')); | ||
|
|
||
| document.getElementById(viewId).classList.add('active'); | ||
| document.getElementById('btn-' + viewId.split('-')[1]).classList.add('active'); | ||
|
|
||
| if(viewId === 'view-3d') resizeThree(); | ||
| } |
There was a problem hiding this comment.
To optimize performance and prevent unnecessary CPU/GPU usage, we should track whether the 3D view is active and pause rendering when the user switches to other tabs.
| function switchView(viewId) { | |
| document.querySelectorAll('.view-container').forEach(el => el.classList.remove('active')); | |
| document.querySelectorAll('.nav-controls button').forEach(el => el.classList.remove('active')); | |
| document.getElementById(viewId).classList.add('active'); | |
| document.getElementById('btn-' + viewId.split('-')[1]).classList.add('active'); | |
| if(viewId === 'view-3d') resizeThree(); | |
| } | |
| let is3DActive = true; | |
| function switchView(viewId) { | |
| document.querySelectorAll('.view-container').forEach(el => el.classList.remove('active')); | |
| document.querySelectorAll('.nav-controls button').forEach(el => el.classList.remove('active')); | |
| document.getElementById(viewId).classList.add('active'); | |
| document.getElementById('btn-' + viewId.split('-')[1]).classList.add('active'); | |
| is3DActive = (viewId === 'view-3d'); | |
| if(is3DActive) resizeThree(); | |
| } |
| function animate() { | ||
| requestAnimationFrame(animate); | ||
|
|
||
| if(particles) { |
There was a problem hiding this comment.
| // Trigger MathJax render | ||
| MathJax.Hub.Queue(["Typeset", MathJax.Hub, "tt-formula"]); |
There was a problem hiding this comment.
Add a defensive check to ensure MathJax and MathJax.Hub are fully loaded before calling Queue(). Since MathJax loads asynchronously, this prevents potential runtime TypeError crashes if the user hovers over a particle before MathJax is ready.
| // Trigger MathJax render | |
| MathJax.Hub.Queue(["Typeset", MathJax.Hub, "tt-formula"]); | |
| // Trigger MathJax render | |
| if (typeof MathJax !== 'undefined' && MathJax.Hub) { | |
| MathJax.Hub.Queue(["Typeset", MathJax.Hub, "tt-formula"]); | |
| } |
| container.innerHTML = html; | ||
| MathJax.Hub.Queue(["Typeset", MathJax.Hub, container]); |
There was a problem hiding this comment.
Add a defensive check to ensure MathJax and MathJax.Hub are fully loaded before rendering the glossary.
| container.innerHTML = html; | |
| MathJax.Hub.Queue(["Typeset", MathJax.Hub, container]); | |
| container.innerHTML = html; | |
| if (typeof MathJax !== 'undefined' && MathJax.Hub) { | |
| MathJax.Hub.Queue(["Typeset", MathJax.Hub, container]); | |
| } |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@biostats_immersive.html`:
- Around line 7-8: The two external script tags in biostats_immersive.html load
third-party assets without integrity pinning, so update the page to either
self-host the three.js and MathJax assets or add SRI to each script reference
and any required crossorigin settings. Keep the fix localized to the existing
script includes so the page still loads the same libraries, just from
trusted/pinned sources.
- Around line 703-704: The Prevalence slider in the simulation is only updating
the UI label and is not being used in the PPV calculation, so the “Given current
prevalence” note is misleading. Update the PPV logic to read the current value
from the in-prev control and incorporate it into the calculation flow used by
the affected UI/update function(s) that render the TP/FP-based results, ensuring
the displayed prevalence actually changes the simulated outcome instead of only
the text.
- Line 697: The calc-mode selector advertises a specificity option, but the
calculation logic still always uses the sensitivity path. Update the mode
handling in the calc flow (including the related UI around calc-mode and the
calculation routine that computes TP/FN) so selecting “spec” switches to a
specificity branch that uses TN/FP instead of TP/FN, and ensure the displayed
result label matches the selected mode.
- Around line 655-680: The flashcard interaction in the flashcard markup is
mouse-only because the flip target is a clickable div with an onclick handler,
so keyboard users cannot access the back face. Update the flashcard container
and/or the card faces to be keyboard-operable by adding appropriate interactive
semantics and a keyboard event handler that triggers the same flip behavior as
the existing onclick on the flashcard element. Ensure the fix preserves the
current flip behavior while making the term/definition content reachable from
the keyboard.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 35fbe702-04bc-4f9f-9683-8f6c4c440ca9
📒 Files selected for processing (2)
.gitignorebiostats_immersive.html
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | ||
| <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML"></script> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Add SRI or self-host these third-party scripts.
Both CDN assets execute with full origin privileges, but the tags do not pin the content. A compromised CDN response here becomes arbitrary script execution inside the app.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biostats_immersive.html` around lines 7 - 8, The two external script tags in
biostats_immersive.html load third-party assets without integrity pinning, so
update the page to either self-host the three.js and MathJax assets or add SRI
to each script reference and any required crossorigin settings. Keep the fix
localized to the existing script includes so the page still loads the same
libraries, just from trusted/pinned sources.
| <div class="flashcard" onclick="this.classList.toggle('flipped')"> | ||
| <div class="card-face card-front"> | ||
| <div class="badge">BIOSTATISTICS CORE</div> | ||
| <div class="term-title">${item.term}</div> | ||
| <div class="term-latex">$${item.latex}$</div> | ||
| <div style="color:#666; font-size:0.8rem; margin-top:20px;">(Tap to Flip)</div> | ||
| </div> | ||
| <div class="card-face card-back"> | ||
| <div class="detail-row"> | ||
| <span class="detail-label">Definition</span> | ||
| <div class="detail-content">${item.desc}</div> | ||
| </div> | ||
| <div class="detail-row"> | ||
| <span class="detail-label">Clinical Context</span> | ||
| <div class="detail-content">${item.clinical}</div> | ||
| </div> | ||
| <div class="detail-row"> | ||
| <span class="detail-label">Exam Trap ⚠️</span> | ||
| <div class="detail-content exam-trap">${item.trap}</div> | ||
| </div> | ||
| <div class="detail-row"> | ||
| <span class="detail-label">Mnemonic</span> | ||
| <div class="detail-content" style="color:var(--neon-blue)">${item.mnemonic}</div> | ||
| </div> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Make the flashcards keyboard-operable.
The glossary’s primary interaction is mouse-only right now. Because the flip target is a plain clickable <div>, keyboard users cannot open the back face and reach the definitions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biostats_immersive.html` around lines 655 - 680, The flashcard interaction in
the flashcard markup is mouse-only because the flip target is a clickable div
with an onclick handler, so keyboard users cannot access the back face. Update
the flashcard container and/or the card faces to be keyboard-operable by adding
appropriate interactive semantics and a keyboard event handler that triggers the
same flip behavior as the existing onclick on the flashcard element. Ensure the
fix preserves the current flip behavior while making the term/definition content
reachable from the keyboard.
| inputs = ` | ||
| <div class="slider-group"><label>True Positives (TP)</label><input type="number" id="in-tp" value="80"></div> | ||
| <div class="slider-group"><label>False Negatives (FN)</label><input type="number" id="in-fn" value="20"></div> | ||
| <div class="slider-group"><label>Mode</label><select id="calc-mode"><option value="sens">Calculate Sensitivity</option><option value="spec">Calculate Specificity (Need TN/FP)</option></select></div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The specificity path is advertised but never implemented.
calc-mode offers “Calculate Specificity”, but the calculation branch always returns sensitivity from TP and FN. Selecting specificity still yields a sensitivity result.
Also applies to: 760-765
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biostats_immersive.html` at line 697, The calc-mode selector advertises a
specificity option, but the calculation logic still always uses the sensitivity
path. Update the mode handling in the calc flow (including the related UI around
calc-mode and the calculation routine that computes TP/FN) so selecting “spec”
switches to a specificity branch that uses TN/FP instead of TP/FN, and ensure
the displayed result label matches the selected mode.
| <div class="slider-group"><label>Prevalence (%)</label><input type="range" id="in-prev" min="1" max="99" value="10" oninput="document.getElementById('prev-val').innerText=this.value+'%'"><span id="prev-val" style="float:right; color:white">10%</span></div> | ||
| <small style="color:#666">*Note: Changing prevalence updates TP/FP ratio automatically for simulation</small> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The PPV prevalence control is currently cosmetic.
The slider only changes the label text. The calculation still uses TP / (TP + FP) and never reads in-prev, so the note and the “Given current prevalence” interpretation are both inaccurate.
Also applies to: 766-771
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@biostats_immersive.html` around lines 703 - 704, The Prevalence slider in the
simulation is only updating the UI label and is not being used in the PPV
calculation, so the “Given current prevalence” note is misleading. Update the
PPV logic to read the current value from the in-prev control and incorporate it
into the calculation flow used by the affected UI/update function(s) that render
the TP/FP-based results, ensuring the displayed prevalence actually changes the
simulated outcome instead of only the text.
This PR was created by qwen-chat coder for task 0bed78ae-df3d-464d-aeb1-c6de5e2d1f60.
Summary by CodeRabbit