towerops/lib/towerops_web/components/layouts/root.html.heex
Graham McIntire e97748437c build(assets): split coverage hooks into a lazy-loaded chunk
Switch esbuild to ESM with --splitting and load app.js as type=module.
Heavy hooks now ship in their own chunk and only download when their
LiveView mounts.

- assets/js/lib/leaflet.ts: extract ensureLeaflet (vendored Leaflet
  loader) into a shared module with a typed window.L declaration.
- assets/js/hooks/coverage_hooks.ts: extract CoverageMap,
  MultiCoverageMap, and CoverageLocationPicker (~510 lines) with
  proper interface types for hook context and tightened typing on
  payloads / event handlers (CoveragePayload is now a real type).
- app.ts: replace those three inline hook bodies with lazyHook
  stubs that call import('./hooks/coverage_hooks') on first mount,
  forwarding lifecycle hooks once the chunk resolves. Also adds a
  scoped `declare const L: any` so the remaining Leaflet hooks
  type-check.
- root.html.heex: switch the script tag to type=module so the
  emitted ESM bundle can use dynamic import().
- esbuild config: add --splitting --format=esm.

Build output now produces:
  app.js                      1.2 MB
  coverage_hooks-<hash>.js   16  KB  (lazy)
  chunk-<hash>.js             2.4 KB  (shared, lazy)
2026-05-06 16:11:32 -05:00

94 lines
3.4 KiB
Text

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content={get_csrf_token()} />
<meta
name="description"
content="TowerOps — Operational intelligence for WISP and ISP operators. Connect network monitoring to subscriber impact and revenue."
/>
<meta name="theme-color" content="#2563eb" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="TowerOps" />
<meta
property="og:title"
content="TowerOps — See the Business Impact of Every Network Event"
/>
<meta
property="og:description"
content="The only platform that connects network monitoring to subscriber impact and revenue. Built for WISP and ISP operators."
/>
<!-- Favicon -->
<link id="favicon" rel="icon" type="image/png" href={~p"/images/towerops_logo.png"} />
<link rel="apple-touch-icon" href={~p"/images/towerops_logo.png"} />
<.live_title suffix=" | TowerOps">
{status_title(assigns)}
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
<script phx-track-static type="module" src={~p"/assets/js/app.js"}>
</script>
<script>
(() => {
const mql = window.matchMedia("(prefers-color-scheme: dark)");
const getSystemTheme = () => mql.matches ? "dark" : "light";
const applyTheme = () => {
const preference = localStorage.getItem("phx:theme") || "system";
const theme = preference === "system" ? getSystemTheme() : preference;
document.documentElement.setAttribute("data-theme", theme);
};
const setTheme = (preference) => {
if (preference === "system") {
localStorage.removeItem("phx:theme");
} else {
localStorage.setItem("phx:theme", preference);
}
applyTheme();
};
// Apply theme on page load
applyTheme();
// Listen for system preference changes (for auto mode)
mql.addEventListener("change", applyTheme);
// Listen for storage changes from other tabs
window.addEventListener("storage", (e) => {
if (e.key === "phx:theme") applyTheme();
});
// Listen for theme change events from LiveView
window.addEventListener("phx:set-theme", (e) => setTheme(e.target.dataset.phxTheme));
})();
</script>
<script>
// Apply sidebar collapsed state before paint (prevents flash)
if (localStorage.getItem('sidebarCollapsed') === 'true') {
document.documentElement.classList.add('sidebar-collapsed');
}
</script>
<!-- Privacy-friendly analytics by Plausible -->
<script async src="https://a.w5isp.com/js/pa-zV14OUOLAl7IniK-HvUsU.js">
</script>
<script>
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
plausible.init()
</script>
</head>
<body>
<a
href="#main-content"
class="sr-only focus:not-sr-only focus:absolute focus:top-0 focus:left-0 focus:z-50 focus:p-4 focus:bg-blue-600 focus:text-white focus:rounded-md focus:m-2"
>
{t("Skip to main content")}
</a>
{@inner_content}
</body>
</html>