Skip to content

Decide JS module boundary before the cruft accretes further #643

@srid

Description

@srid

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions