aboutsummaryrefslogtreecommitdiffstats
path: root/src/app.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-05 19:46:48 -0700
committerAdam Soltys <[email protected]>2026-09-05 19:46:48 -0700
commitc8c18d838573859b61c47875a4320b9ad0ad7514 (patch)
treebd0e28438a2fa6bb52dffcd55cc8490b0ad0f8b8 /src/app.js
parentc99efea0ab7a724221d439bbc227058223dfe3c4 (diff)
downloadcoinosv3-c8c18d838573859b61c47875a4320b9ad0ad7514.tar.gz
coinosv3-c8c18d838573859b61c47875a4320b9ad0ad7514.zip
morph: leave <details open> to the user; key the names settings sections
Every background repaint stripped the browser-set `open` attribute (the fresh render never carries it), collapsing expanded sections, and the positional child match handed one section's open state to its neighbour whenever the conditional zap-code section came or went. `open` on a details is now user-owned unless a render sets it, and nodes carrying data-key only patch into the same key. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VMi2Y1oKPZ3TeeFmLej47y
Diffstat (limited to 'src/app.js')
-rw-r--r--src/app.js14
1 files changed, 13 insertions, 1 deletions
diff --git a/src/app.js b/src/app.js
index 5013058..e8a308d 100644
--- a/src/app.js
+++ b/src/app.js
@@ -180,8 +180,20 @@ function morph(a, b) {
// would visibly crossfade the old style into the new one (a dark filled
// button melting into a text link read as a black flash). Repaints aren't
// style animations: snap it, restore the transition after the paint.
+ // A node with a data-key is only ever patched into a node with the SAME
+ // key: siblings line up by position, so a conditional sibling appearing or
+ // vanishing would otherwise hand one section's live state (an expanded
+ // <details>) to its neighbour.
+ if (a.getAttribute('data-key') !== b.getAttribute('data-key')) {
+ a.replaceWith(b);
+ return;
+ }
const snap = a.nodeName === 'BUTTON' && a.getAttribute('class') !== b.getAttribute('class');
- for (const at of [...a.attributes]) if (!b.hasAttribute(at.name)) a.removeAttribute(at.name);
+ // <details open> is the user's doing (the browser toggles the attribute on
+ // click), like a field's value: a render that doesn't set `open` leaves it
+ // alone instead of collapsing the section on every background repaint.
+ const userOwned = (at) => a.nodeName === 'DETAILS' && at.name === 'open';
+ for (const at of [...a.attributes]) if (!b.hasAttribute(at.name) && !userOwned(at)) a.removeAttribute(at.name);
for (const at of [...b.attributes]) if (a.getAttribute(at.name) !== at.value) a.setAttribute(at.name, at.value);
if (snap) {
a.style.transition = 'none';