fix: Prevent scroll from affecting parent page in iframes

scrollIntoView() scrolls all ancestor containers including the parent
page when embedded in an iframe. Replaced with manual scrollTo() on
the specific container to isolate scroll behavior.

- Use container.scrollTo() instead of element.scrollIntoView()
- Add browser fallback for older browsers without scrollTo support
- Add Math.max(0, ...) guard for edge cases
- Remove redundant scroll-margin-top CSS (only affects scrollIntoView)
This commit is contained in:
lennyzeltser
2026-01-16 14:18:03 -05:00
parent 36f2a6083a
commit 16e33e469f
6 changed files with 29 additions and 2078 deletions
+10 -4
View File
@@ -711,13 +711,12 @@
flex-direction: column;
align-items: center;
text-align: center;
font-size: 13px;
font-weight: 500;
color: var(--transition-text, var(--text-muted));
opacity: 0;
transform: translateY(10px);
padding: 0 40px;
}
@@ -1281,7 +1280,7 @@
<a href="https://github.com/lennyzeltser/conversation-replay" target="_blank" rel="noopener noreferrer" class="info-title-link">Conversation Replay</a>
<div class="info-meta">
<span>Created by <a href="https://zeltser.com" target="_blank" rel="noopener noreferrer" class="info-link">Lenny Zeltser</a></span>
<span>Version 0.1.16</span>
<span>Version 0.1.17</span>
</div>
</div>
</div>
@@ -1537,7 +1536,14 @@
}
function scrollToNewElement(element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
var container = chatMessages.parentElement;
var scrollMargin = 8;
var elementTop = Math.max(0, element.offsetTop - scrollMargin);
if (typeof container.scrollTo === 'function') {
container.scrollTo({ top: elementTop, behavior: 'smooth' });
} else {
container.scrollTop = elementTop;
}
}
function createStepElement(step) {
+10 -4
View File
@@ -563,13 +563,12 @@
flex-direction: column;
align-items: center;
text-align: center;
font-size: 13px;
font-weight: 500;
color: var(--transition-text, var(--text-muted));
opacity: 0;
transform: translateY(10px);
padding: 0 40px;
}
@@ -1119,7 +1118,7 @@
<a href="https://github.com/lennyzeltser/conversation-replay" target="_blank" rel="noopener noreferrer" class="info-title-link">Conversation Replay</a>
<div class="info-meta">
<span>Created by <a href="https://zeltser.com" target="_blank" rel="noopener noreferrer" class="info-link">Lenny Zeltser</a></span>
<span>Version 0.1.16</span>
<span>Version 0.1.17</span>
</div>
</div>
</div>
@@ -1365,7 +1364,14 @@
}
function scrollToNewElement(element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
var container = chatMessages.parentElement;
var scrollMargin = 8;
var elementTop = Math.max(0, element.offsetTop - scrollMargin);
if (typeof container.scrollTo === 'function') {
container.scrollTo({ top: elementTop, behavior: 'smooth' });
} else {
container.scrollTop = elementTop;
}
}
function createStepElement(step) {
File diff suppressed because it is too large Load Diff
-52
View File
@@ -1,52 +0,0 @@
# Scroll Test - Long Message Demo
# Tests that new messages scroll to show the TOP of the bubble
meta:
title: "Scroll Test - Long Messages"
description: "Testing scroll behavior with long message bubbles"
theme: chat
scenarios:
- id: main
title: "Long Message Test"
participants:
- id: user
label: "User"
role: left
- id: assistant
label: "Assistant"
role: right
steps:
- type: message
from: user
content: "Can you explain the key principles of secure software development?"
- type: message
from: assistant
content: |
Here are the fundamental principles of secure software development that every developer should understand and apply throughout the software development lifecycle. These principles form the foundation of building resilient, trustworthy applications.
1. DEFENSE IN DEPTH - Never rely on a single security control. Layer multiple defenses so that if one fails, others still protect the system. This includes network segmentation, application firewalls, input validation, and encryption.
2. PRINCIPLE OF LEAST PRIVILEGE - Grant users and processes only the minimum permissions they need to perform their tasks. This limits the potential damage from compromised accounts or vulnerabilities.
3. FAIL SECURELY - When errors occur, ensure the system fails in a secure state. Don't expose sensitive information in error messages, and don't leave resources in an insecure state after failures.
4. INPUT VALIDATION - Never trust user input. Validate all input on the server side, use allowlists rather than blocklists, and sanitize data before use in queries or output.
5. SEPARATION OF DUTIES - Critical operations should require multiple parties. No single person should be able to compromise the entire system.
6. KEEP SECURITY SIMPLE - Complex security mechanisms are harder to understand, implement correctly, and maintain. Simple designs are more likely to be secure.
7. FIX SECURITY ISSUES CORRECTLY - When fixing vulnerabilities, understand the root cause. Quick patches often introduce new problems or fail to address the underlying issue.
- type: annotation
content: "The message above is intentionally long to test scroll behavior on mobile devices."
- type: message
from: user
content: "That's very helpful, thank you!"
- type: message
from: assistant
content: "You're welcome! Remember that security is an ongoing process, not a one-time task."
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "conversation-replay",
"version": "0.1.17",
"version": "0.1.18",
"description": "Create annotated replays of text conversations",
"main": "dist/cli.js",
"type": "module",
+8 -6
View File
@@ -735,7 +735,6 @@ function generateCss(theme: Theme, hasMultipleScenarios: boolean, colors?: Color
opacity: 0;
transform: translateY(12px) scale(0.98);
transform-origin: bottom center;
scroll-margin-top: 8px;
}
.message.visible {
@@ -847,7 +846,6 @@ function generateCss(theme: Theme, hasMultipleScenarios: boolean, colors?: Color
color: var(--annotation-text);
opacity: 0;
transform: translateY(10px);
scroll-margin-top: 8px;
}
.annotation::before {
@@ -899,8 +897,6 @@ function generateCss(theme: Theme, hasMultipleScenarios: boolean, colors?: Color
color: var(--transition-text, var(--text-muted));
opacity: 0;
transform: translateY(10px);
scroll-margin-top: 8px;
padding: 0 40px;
}
@@ -935,7 +931,6 @@ function generateCss(theme: Theme, hasMultipleScenarios: boolean, colors?: Color
font-size: 13px;
opacity: 0;
transition: opacity 0.4s ease;
scroll-margin-top: 8px;
}
.scenario-ending.visible {
@@ -1618,7 +1613,14 @@ function generateJs(demo: Demo, timerStyle: TimerStyle): string {
}
function scrollToNewElement(element) {
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
var container = chatMessages.parentElement;
var scrollMargin = 8;
var elementTop = Math.max(0, element.offsetTop - scrollMargin);
if (typeof container.scrollTo === 'function') {
container.scrollTo({ top: elementTop, behavior: 'smooth' });
} else {
container.scrollTop = elementTop;
}
}
function createStepElement(step) {