Skip to content

Commit 8244c18

Browse files
authored
fix: qa issues
Issues: - [Optimism Handbook] Disclaimer modal unexpected behavior [CHA-356](https://linear.app/defi-wonderland/issue/CHA-356/optimism-handbook-disclaimer-modal-unexpected-behavior) - [Optimism Handbook] "Last update" text is too small [CHA-355](https://linear.app/defi-wonderland/issue/CHA-355/optimism-handbook-last-update-text-is-too-small) - [Optimism Handbook] <em> tag formatting in title results in font inconsistency [CHA-354](https://linear.app/defi-wonderland/issue/CHA-354/optimism-handbook-em-tag-formatting-in-title-results-in-font) - [Wonderland Handbook] Code block misplaced [CHA-350](https://linear.app/defi-wonderland/issue/CHA-350/wonderland-handbook-code-block-misplaced) - [Wonderland Handbook] Titles centered instead of left-aligned [CHA-349](https://linear.app/defi-wonderland/issue/CHA-349/wonderland-handbook-titles-centered-instead-of-left-aligned) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved the reliability of the disclaimer button’s click functionality, ensuring it works correctly across navigation events and dynamic page updates. * **Style** * Updated menu link alignment to left-align text. * Added a new style for displaying the last updated timestamp. * **Documentation** * Improved formatting and readability in interoperability and advanced testing documentation. * Standardized warning blocks and enhanced emphasis formatting for clarity. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 32aa48a commit 8244c18

4 files changed

Lines changed: 193 additions & 39 deletions

File tree

packages/common-config/src/components/theme/Root/index.tsx

Lines changed: 162 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,161 @@ function SimpleModal({ isOpen, onClose, children }: SimpleModalProps) {
6060
);
6161
}
6262

63+
/**
64+
* Manages the disclaimer button event listener setup and cleanup
65+
* This function is extracted to prevent multiple event listener attachments
66+
* during Docusaurus navigation
67+
*/
68+
function createDisclaimerButtonManager(onDisclaimerClick: () => void) {
69+
let isListenerAttached = false;
70+
let pollInterval: NodeJS.Timeout | undefined;
71+
let currentButton: HTMLElement | null = null;
72+
let mutationObserver: MutationObserver | null = null;
73+
74+
const attachListener = () => {
75+
const disclaimerBtn = document.getElementById("disclaimer-btn");
76+
77+
// Check if this is a different button or if we need to reattach
78+
if (
79+
disclaimerBtn &&
80+
(disclaimerBtn !== currentButton || !isListenerAttached)
81+
) {
82+
// Remove listener from old button if it exists
83+
if (currentButton && isListenerAttached) {
84+
currentButton.removeEventListener("click", onDisclaimerClick);
85+
}
86+
87+
disclaimerBtn.addEventListener("click", onDisclaimerClick);
88+
currentButton = disclaimerBtn;
89+
isListenerAttached = true;
90+
91+
if (pollInterval) {
92+
clearInterval(pollInterval);
93+
pollInterval = undefined;
94+
}
95+
return true;
96+
}
97+
return false;
98+
};
99+
100+
const resetAndReattach = () => {
101+
isListenerAttached = false;
102+
currentButton = null;
103+
104+
// Try to attach immediately
105+
if (!attachListener()) {
106+
// If button not found, start polling
107+
if (!pollInterval) {
108+
pollInterval = setInterval(() => {
109+
attachListener();
110+
}, 100);
111+
}
112+
}
113+
};
114+
115+
const setup = () => {
116+
if (typeof window === "undefined") return () => {};
117+
118+
// Try to attach immediately
119+
resetAndReattach();
120+
121+
// Set up MutationObserver to watch for DOM changes
122+
mutationObserver = new MutationObserver((mutations) => {
123+
let shouldReattach = false;
124+
125+
mutations.forEach((mutation) => {
126+
// Check if nodes were added or removed
127+
if (mutation.type === "childList") {
128+
// Check if our current button is still in the DOM
129+
if (currentButton && !document.contains(currentButton)) {
130+
shouldReattach = true;
131+
}
132+
// Check if a new disclaimer button was added
133+
mutation.addedNodes.forEach((node) => {
134+
if (node.nodeType === Node.ELEMENT_NODE) {
135+
const element = node as Element;
136+
if (
137+
element.id === "disclaimer-btn" ||
138+
element.querySelector("#disclaimer-btn")
139+
) {
140+
shouldReattach = true;
141+
}
142+
}
143+
});
144+
}
145+
});
146+
147+
if (shouldReattach) {
148+
resetAndReattach();
149+
}
150+
});
151+
152+
// Start observing the navbar area specifically
153+
const navbar = document.querySelector(
154+
"[role='banner'], .navbar, .navbar__inner"
155+
);
156+
if (navbar) {
157+
mutationObserver.observe(navbar, {
158+
childList: true,
159+
subtree: true,
160+
});
161+
} else {
162+
// Fallback to observing the entire body
163+
mutationObserver.observe(document.body, {
164+
childList: true,
165+
subtree: true,
166+
});
167+
}
168+
169+
// Also listen for route change events specific to Docusaurus
170+
const handleDocusaurusRouteChange = () => {
171+
setTimeout(resetAndReattach, 100);
172+
};
173+
174+
// Listen for various navigation events
175+
window.addEventListener("popstate", handleDocusaurusRouteChange);
176+
window.addEventListener("routeUpdate", handleDocusaurusRouteChange);
177+
178+
// Intercept history methods for programmatic navigation
179+
const originalPushState = history.pushState;
180+
const originalReplaceState = history.replaceState;
181+
182+
history.pushState = function (...args) {
183+
originalPushState.apply(history, args);
184+
handleDocusaurusRouteChange();
185+
};
186+
187+
history.replaceState = function (...args) {
188+
originalReplaceState.apply(history, args);
189+
handleDocusaurusRouteChange();
190+
};
191+
192+
// Return cleanup function
193+
return () => {
194+
if (pollInterval) {
195+
clearInterval(pollInterval);
196+
}
197+
198+
if (currentButton && isListenerAttached) {
199+
currentButton.removeEventListener("click", onDisclaimerClick);
200+
}
201+
202+
if (mutationObserver) {
203+
mutationObserver.disconnect();
204+
}
205+
206+
window.removeEventListener("popstate", handleDocusaurusRouteChange);
207+
window.removeEventListener("routeUpdate", handleDocusaurusRouteChange);
208+
209+
// Restore original methods
210+
history.pushState = originalPushState;
211+
history.replaceState = originalReplaceState;
212+
};
213+
};
214+
215+
return { setup };
216+
}
217+
63218
interface RootProps {
64219
children: React.ReactNode;
65220
disclaimerContent?: React.ReactNode | string;
@@ -74,28 +229,18 @@ export default function Root({
74229
const [showModal, setShowModal] = useState(false);
75230

76231
const handleCloseModal = useCallback(() => setShowModal(false), []);
232+
const handleDisclaimerClick = useCallback(() => setShowModal(true), []);
77233

78234
useEffect(() => {
79235
if (!showDisclaimer) return;
80236

81-
const handleNavbarClick = () => {
82-
setShowModal(true);
83-
};
84-
85-
// Add listener when component mounts
86-
const disclaimerBtn = document.getElementById("disclaimer-btn");
87-
if (disclaimerBtn) {
88-
disclaimerBtn.addEventListener("click", handleNavbarClick);
89-
}
237+
const disclaimerManager = createDisclaimerButtonManager(
238+
handleDisclaimerClick
239+
);
240+
const cleanup = disclaimerManager.setup();
90241

91-
// Cleanup listener when component unmounts
92-
return () => {
93-
const disclaimerBtn = document.getElementById("disclaimer-btn");
94-
if (disclaimerBtn) {
95-
disclaimerBtn.removeEventListener("click", handleNavbarClick);
96-
}
97-
};
98-
}, [showDisclaimer]);
242+
return cleanup;
243+
}, [showDisclaimer, handleDisclaimerClick]);
99244

100245
return (
101246
<>

packages/common-config/static/common/styles/global.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ a:hover {
739739
text-align: center;
740740
border-radius: 0.5rem;
741741
margin-right: 0.2rem;
742+
text-align: start;
742743
}
743744

744745
.menu__list .menu__link:hover {
@@ -1589,3 +1590,7 @@ html {
15891590
scrollbar-width: thin;
15901591
scrollbar-color: var(--wonderland-gray-800) var(--wonderland-blue-950);
15911592
}
1593+
1594+
.theme-last-updated {
1595+
font-size: 1.3rem;
1596+
}

sites/optimism/docs/interoperability/intro-to-interop.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ id: intro-to-interop
33
title: Mental model
44
sidebar_label: Mental model
55
---
6-
This section explains the foundational mental model of OP Stack interoperability, from how messages are emitted and consumed to the validation logic that makes them safe.
76

8-
When we say *interoperability*, we are talking about the ability of one chain to read and act on messages from another chain.
7+
This section explains the foundational mental model of OP Stack interoperability, from how messages are emitted and consumed to the validation logic that makes them safe.
8+
9+
When we say _interoperability_, we are talking about the ability of one chain to read and act on messages from another chain.
910

1011
In the OP stack, interop is built into the protocol. Chains should be able to emit logs, call initiating messages, and other chains should be able to consume them as inputs to their own transactions. **This does not require routing through Ethereum L1.**
1112

@@ -22,6 +23,7 @@ Native interop solves this by allowing chains to read logs from each other witho
2223
Interop is based on events. One chain emits an event. Another chain sees it and takes action.
2324

2425
Each cross-chain message has two parts:
26+
2527
- **Initiating message**: A log emitted on the source chain.
2628
- **Executing message**: A log emitted on the destination chain, pointing to the initiating message.
2729

@@ -36,17 +38,19 @@ struct Identifier {
3638
uint256 chainid;
3739
}
3840
```
41+
3942
This identifier is used to prove that the message exists and that it has not expired.
4043

4144
### Validity
4245

4346
A message can only be consumed if it is valid. There are three invariants:
47+
4448
- **Timestamp**: The initiating message must have a timestamp less than or equal to the executing message.
4549
- **Chain ID**: The source chain must be in the destination chain’s dependency set.
4650
- **Expiry**: The message must be consumed within a fixed time window after it is created. This window is currently set to 180 days.
4751

48-
:::info
49-
If this time window elapses, the message can be re-sent on origin to make it available using `L2ToL2CrossDomainMessenger#resendMessage`.
52+
:::info
53+
If this time window elapses, the message can be re-sent on origin to make it available using `L2ToL2CrossDomainMessenger#resendMessage`.
5054
:::
5155

5256
If any of these invariants fail, the executing message is invalid. Any block that includes it will be reorged.
@@ -65,12 +69,12 @@ This configuration tells the node **which chains to watch**, **which logs to ind
6569

6670
:::info Can I just depend on OP Mainnet?
6771

68-
No. Each chain’s dependency set must explicitly include the chains it wants to receive messages from. Depending only on OP Mainnet does *not* automatically grant interoperability with every other OP Chain.
72+
No. Each chain’s dependency set must explicitly include the chains it wants to receive messages from. Depending only on OP Mainnet does _not_ automatically grant interoperability with every other OP Chain.
6973

7074
To enable seamless interop, chains should include all intended counterparties in their dependency set. This is why Superchain clusters aim to configure **fully connected meshes**, where every chain includes all others.
7175
:::
7276

73-
### Who *enforces* interop?
77+
### Who enforces interop?
7478

7579
Most of the interop logic is enforced by the **consensus node software**, not by contracts.
7680

sites/wonderland/docs/testing/advanced-testing/overview.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,22 @@ function testOne() public {
9999
- Check the coverage to see if the test itself is not (silently) reverting and if the target code is actually covered
100100
- Make sure both sides of a revert are covered with try - assert - catch - assert.
101101
- Mutate the property itself or the target code. See [Mutation testing](../mutation-testing.md) for more details
102-
<aside>
103-
⚠️
104-
105-
Unlike what you could be used to in regular unit or integration tests, only assert with a condition equals to false will make a test revert - without the previous checks, it is therefore easy to take a passing test for granted, especially as most contracts are using require and revert (in other words, if one doesnt pay attention, one can easily end up testing nothing).
106-
107-
```solidity
108-
function testOne() public {
109-
require(false);
110-
}
111-
112-
function testTwo public {
113-
assert(false);
114-
}
115-
```
116-
117-
</aside>
102+
103+
:::warning
104+
105+
Unlike what you could be used to in regular unit or integration tests, only assert with a condition equals to false will make a test revert - without the previous checks, it is therefore easy to take a passing test for granted, especially as most contracts are using require and revert (in other words, if one doesn't pay attention, one can easily end up testing nothing).
106+
107+
```solidity
108+
function testOne() public {
109+
require(false);
110+
}
111+
112+
function testTwo public {
113+
assert(false);
114+
}
115+
```
116+
117+
:::
118118

119119
- Any state which can be accessed or reconstructed from the target should be (even if some needs to be recomputed, for instance reducing individual balances to a cumulative sum). If there is no way to do so, then a ghost variable should be used (either in the relevant handler, or, usually easier, in a “BaseHandler” contract, with relevant helper functions).
120120
- Always start with validating the initial setup and sanity checks as “property-0”. For instance, address of the deployed contract ≠ 0, calling some constant variables, etc.

0 commit comments

Comments
 (0)