Promoted out of #632 (section 3, JS module boundary) so it can be scheduled on its own — the tracking issue gates section 4 on this decision, but the decision also blocks further interactive JS anywhere, and the list of places that need it keeps growing.
Every new interactive feature lands as another inline IIFE in a Heist template, and the count is now five. Left alone, section 4 adds command-palette, keyboard nav, and recently-viewed, each naturally pulling the same gravity.
Current inventory of inline interactive JS, by file:
| Template |
Behavior |
Rough size |
emanote/default/templates/components/stork/stork-search-head.tpl:17-99 |
Stork search controller — modal open/close, keyboard shortcuts, index loader |
~80 lines |
emanote/default/templates/components/styles.tpl:289-305 |
Theme toggle — persists in localStorage, publishes window.emanote.theme.toggle |
~15 lines |
emanote/default/templates/components/styles.tpl:307-344 |
Code-copy-button injection — creates a button per <pre>, clipboard write + 2s revert |
~40 lines |
emanote/default/templates/components/toc.tpl:21-73 |
TOC scroll tracker — window.onscroll + offsetTop arithmetic; issue #520 already wants IntersectionObserver |
~50 lines |
emanote/default/templates/components/breadcrumbs.tpl:41-45 |
toggleHidden() sidebar class-toggle |
~5 lines |
emanote/default/templates/components/footnote-popup.tpl |
Popup footnotes — popover lifecycle, scope-aware target resolution, viewport repositioning, live-server reconnection guards (landed in #642) |
~115 lines |
That last row is new in PR #642 and is exactly the pattern #632's section 3 names as the failure mode: a self-contained IIFE in a .tpl that reaches into DOM via data-* attributes, colocated with the CSS it styles. It is individually defensible (the attribute contract is deliberate, the lifecycle is self-contained) but it is another data point on the same trajectory — six files now share a pattern nobody has signed off as the house style.
What needs to be decided
- Module shape. One bundled file loaded once from
_emanote-static/ vs. module scripts co-located with templates vs. Web Components. The tracking issue mentions Alpine.js (~15kb, declarative), Web Components (zero-dep, boilerplate), or vanilla-with-a-helper. Pick one and commit before the next IIFE gets written.
- State-passing contract.
data-* attributes only, no Heist-spliced JS values. This is already de-facto the convention (popup footnotes do exactly this), but it should be documented.
- Where it runs. Loaded once in
base.tpl, module-level IIFEs that idempotently attach on DOMContentLoaded and are resilient to Emanote's live-server DOM patches (the footnote popup already learned this the hard way — ensurePopover / findTarget both do isConnected reconciliation).
- Migration plan. Consolidation order for the six existing scripts. Some are trivial (breadcrumbs toggle is 5 lines and will evaporate into
<details> in section 1 anyway); others (stork, TOC tracker) are non-trivial rewrites that want their own PRs.
Why now
Section 4 of #632 can't start without this. Section 1 is actively adding more inline scripts (#642 added one; native <details> in section 1 will remove one but the trend is clearly upward). Each new IIFE that lands before the boundary is decided is another migration target later.
Blocks #632 section 4. Related: #520 (TOC scroll tracking), #642 (the most recent IIFE).
Promoted out of #632 (section 3, JS module boundary) so it can be scheduled on its own — the tracking issue gates section 4 on this decision, but the decision also blocks further interactive JS anywhere, and the list of places that need it keeps growing.
Every new interactive feature lands as another inline IIFE in a Heist template, and the count is now five. Left alone, section 4 adds command-palette, keyboard nav, and recently-viewed, each naturally pulling the same gravity.
Current inventory of inline interactive JS, by file:
emanote/default/templates/components/stork/stork-search-head.tpl:17-99emanote/default/templates/components/styles.tpl:289-305window.emanote.theme.toggleemanote/default/templates/components/styles.tpl:307-344<pre>, clipboard write + 2s revertemanote/default/templates/components/toc.tpl:21-73window.onscroll+offsetToparithmetic; issue #520 already wantsIntersectionObserveremanote/default/templates/components/breadcrumbs.tpl:41-45toggleHidden()sidebar class-toggleemanote/default/templates/components/footnote-popup.tplThat last row is new in PR #642 and is exactly the pattern #632's section 3 names as the failure mode: a self-contained IIFE in a
.tplthat reaches into DOM viadata-*attributes, colocated with the CSS it styles. It is individually defensible (the attribute contract is deliberate, the lifecycle is self-contained) but it is another data point on the same trajectory — six files now share a pattern nobody has signed off as the house style.What needs to be decided
_emanote-static/vs. module scripts co-located with templates vs. Web Components. The tracking issue mentions Alpine.js (~15kb, declarative), Web Components (zero-dep, boilerplate), or vanilla-with-a-helper. Pick one and commit before the next IIFE gets written.data-*attributes only, no Heist-spliced JS values. This is already de-facto the convention (popup footnotes do exactly this), but it should be documented.base.tpl, module-level IIFEs that idempotently attach onDOMContentLoadedand are resilient to Emanote's live-server DOM patches (the footnote popup already learned this the hard way —ensurePopover/findTargetboth doisConnectedreconciliation).<details>in section 1 anyway); others (stork, TOC tracker) are non-trivial rewrites that want their own PRs.Why now
Section 4 of #632 can't start without this. Section 1 is actively adding more inline scripts (#642 added one; native
<details>in section 1 will remove one but the trend is clearly upward). Each new IIFE that lands before the boundary is decided is another migration target later.Blocks #632 section 4. Related: #520 (TOC scroll tracking), #642 (the most recent IIFE).