Repository: maxence-charriere/go-app (language: go)

Issue:
Maybe a documentation error or outdated?
in https://go-app.dev/js#get-input-value

`v := ctx.JSSrc().Get("value").String()`

I think this should be `ctx.JSSrc.Get("value").String()`


Discussion / hints:

Fix patch:
diff --git a/docs/app-worker.js b/docs/app-worker.js
index 389a8c8c6..87b55b21f 100644
--- a/docs/app-worker.js
+++ b/docs/app-worker.js
@@ -1,7 +1,7 @@
-const cacheName = "app-" + "4f99d197693d33426f4b1a0c7d453d396669955f";
+const cacheName = "app-" + "49698cd8ee2ac62db9b7c99033fb5151a0b61c21";
 
 self.addEventListener("install", event => {
-  console.log("installing app worker 4f99d197693d33426f4b1a0c7d453d396669955f");
+  console.log("installing app worker 49698cd8ee2ac62db9b7c99033fb5151a0b61c21");
   self.skipWaiting();
 
   event.waitUntil(
@@ -38,7 +38,7 @@ self.addEventListener("activate", event => {
       );
     })
   );
-  console.log("app worker 4f99d197693d33426f4b1a0c7d453d396669955f is activated");
+  console.log("app worker 49698cd8ee2ac62db9b7c99033fb5151a0b61c21 is activated");
 });
 
 self.addEventListener("fetch", event => {
diff --git a/docs/app.js b/docs/app.js
index aa2e8c54f..04e5b0ed0 100644
--- a/docs/app.js
+++ b/docs/app.js
@@ -1,11 +1,25 @@
 // -----------------------------------------------------------------------------
 // Init service worker
 // -----------------------------------------------------------------------------
+var goappOnUpdate = function () { };
+
+
 if ("serviceWorker" in navigator) {
   navigator.serviceWorker
     .register("/app-worker.js")
     .then(reg => {
       console.log("registering app service worker");
+
+      reg.onupdatefound = function () {
+        const installingWorker = reg.installing;
+        installingWorker.onstatechange = function () {
+          if (installingWorker.state == "installed") {
+            if (navigator.serviceWorker.controller) {
+              goappOnUpdate();
+            }
+          }
+        };
+      }
     })
     .catch(err => {
       console.error("offline service worker registration failed", err);
@@ -15,7 +29,7 @@ if ("serviceWorker" in navigator) {
 // -----------------------------------------------------------------------------
 // Init progressive app
 // -----------------------------------------------------------------------------
-const goappEnv = {"GOAPP_ROOT_PREFIX":"","GOAPP_STATIC_RESOURCES_URL":"","GOAPP_VERSION":"4f99d197693d33426f4b1a0c7d453d396669955f"};
+const goappEnv = {"GOAPP_ROOT_PREFIX":"","GOAPP_STATIC_RESOURCES_URL":"","GOAPP_VERSION":"49698cd8ee2ac62db9b7c99033fb5151a0b61c21"};
 
 function goappGetenv(k) {
   return goappEnv[k];
diff --git a/docs/architecture.html b/docs/architecture.html
index b718614e5..b17c8d109 100644
--- a/docs/architecture.html
+++ b/docs/architecture.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/built-with.html b/docs/built-with.html
index b718614e5..b17c8d109 100644
--- a/docs/built-with.html
+++ b/docs/built-with.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/components.html b/docs/components.html
index b718614e5..b17c8d109 100644
--- a/docs/components.html
+++ b/docs/components.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/concurrency.html b/docs/concurrency.html
index b718614e5..b17c8d109 100644
--- a/docs/concurrency.html
+++ b/docs/concurrency.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/documentation b/docs/documentation
new file mode 100755
index 000000000..27f333f16
Binary files /dev/null and b/docs/documentation differ
diff --git a/docs/index.html b/docs/index.html
index b718614e5..b17c8d109 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/install.html b/docs/install.html
index b718614e5..b17c8d109 100644
--- a/docs/install.html
+++ b/docs/install.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/js.html b/docs/js.html
index b718614e5..b17c8d109 100644
--- a/docs/js.html
+++ b/docs/js.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/lifecycle.html b/docs/lifecycle.html
new file mode 100644
index 000000000..b17c8d109
--- /dev/null
+++ b/docs/lifecycle.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <meta httpequiv="Content-Type" content="text/html; charset=utf-8">
+        <meta name="author" content="Maxence Charriere">
+        <meta name="description" content="Documentation for the go-app package.">
+        <meta name="keywords" content="go-app, go, golang, app, pwa, progressive web app, webassembly, web assembly, gui, ui, user interface, graphical user interface, frontend, opensource, open source, github">
+        <meta name="theme-color" content="#2e343a">
+        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, viewport-fit=cover">
+        <title>
+            go-app documentation
+        </title>
+        <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
+        <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
+        <link rel="manifest" href="/manifest.webmanifest">
+        <link rel="stylesheet" href="/app.css" type="text/css">
+        <script src="/wasm_exec.js" defer></script>
+        <script defer src="/app.js"></script>
+        <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
+        <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
+        <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
+        <script defer src="/web/js/prism.js"></script>
+        <!-- Global site tag (gtag.js) - Google Analytics -->
+			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
+			<script>
+			  window.dataLayer = window.dataLayer || [];
+			  function gtag(){dataLayer.push(arguments);}
+			  gtag('js', new Date());
+			
+			  gtag('config', 'G-SW4FQEM9VM');
+			</script>
+
+        <meta property="og:url" content="https://go-app.dev">
+
+        <meta property="og:title" content="go-app">
+
+        <meta property="og:description" content="A package to build progressive web apps with Go programming language and WebAssembly.">
+
+        <meta property="og:image" content="https://go-app.dev/web/images/go-app.png">
+
+    </head>
+    <body>
+        <div id="app-wasm-layout" class="goapp-app-info">
+            <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
+            <p id="app-wasm-loader-label" class="goapp-label">
+                Loading go-app documentation...
+            </p>
+        </div>
+        <div id="app-context-menu"></div>
+        <div id="app-end"></div>
+    </body>
+</html>
\ No newline at end of file
diff --git a/docs/reference.html b/docs/reference.html
index b718614e5..b17c8d109 100644
--- a/docs/reference.html
+++ b/docs/reference.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/routing.html b/docs/routing.html
index b718614e5..b17c8d109 100644
--- a/docs/routing.html
+++ b/docs/routing.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/src/menu.go b/docs/src/menu.go
index 54242aa9b..6bb385b5b 100644
--- a/docs/src/menu.go
+++ b/docs/src/menu.go
@@ -109,6 +109,15 @@ func (m *menu) Render() app.UI {
 							Text("JS/Dom").
 							Selected(m.currentPath == "/js").
 							Href("/js"),
+						newMenuItem().
+							Icon(`
+							<svg style="width:24px;height:24px" viewBox="0 0 24 24">
+    							<path fill="currentColor" d="M11,10H5L3,8L5,6H11V3L12,2L13,3V4H19L21,6L19,8H13V10H19L21,12L19,14H13V20A2,2 0 0,1 15,22H9A2,2 0 0,1 11,20V10Z" />
+							</svg>
+							`).
+							Text("Lifecycle").
+							Selected(m.currentPath == "/lifecycle").
+							Href("/lifecycle"),
 						newMenuItem().
 							Icon(`
 							<svg style="width:24px;height:24px" viewBox="0 0 24 24">
diff --git a/docs/src/pages.go b/docs/src/pages.go
index 371310169..f4588945d 100644
--- a/docs/src/pages.go
+++ b/docs/src/pages.go
@@ -28,6 +28,7 @@ func pages() map[string]func() app.UI {
 		"static-resources": newStaticResources,
 		"built-with":       newBuiltWith,
 		"install":          newInstall,
+		"lifecycle":        newLifecycle,
 	}
 }
 
@@ -185,6 +186,18 @@ func newInstall() app.UI {
 		)
 }
 
+func newLifecycle() app.UI {
+	return newPage().
+		Path("/web/documents/lifecycle.md").
+		TableOfContents(
+			"Lifecycle",
+			"    First loading",
+			"    Recurrent loadings",
+			"    Loading after app update",
+			"Listen for updates",
+		)
+}
+
 type page struct {
 	app.Compo
 
@@ -206,6 +219,9 @@ func (p *page) TableOfContents(v ...string) *page {
 	return p
 }
 
+func (p *page) OnAppUpdate(ctx app.Context) {
+}
+
 func (p *page) Render() app.UI {
 	return app.Shell().
 		Class("app-background").
diff --git a/docs/start.html b/docs/start.html
index b718614e5..b17c8d109 100644
--- a/docs/start.html
+++ b/docs/start.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/static-resources.html b/docs/static-resources.html
index b718614e5..b17c8d109 100644
--- a/docs/static-resources.html
+++ b/docs/static-resources.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/syntax.html b/docs/syntax.html
index b718614e5..b17c8d109 100644
--- a/docs/syntax.html
+++ b/docs/syntax.html
@@ -14,13 +14,13 @@
         <link rel="icon" type="image/png" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="apple-touch-icon" href="https://storage.googleapis.com/murlok-github/icon-192.png">
         <link rel="manifest" href="/manifest.webmanifest">
-        <link type="text/css" rel="stylesheet" href="/app.css">
+        <link rel="stylesheet" href="/app.css" type="text/css">
         <script src="/wasm_exec.js" defer></script>
-        <script src="/app.js" defer></script>
+        <script defer src="/app.js"></script>
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500&display=swap">
         <link type="text/css" rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap">
         <link type="text/css" rel="stylesheet" href="/web/css/prism.css">
-        <link rel="stylesheet" href="/web/css/docs.css" type="text/css">
+        <link type="text/css" rel="stylesheet" href="/web/css/docs.css">
         <script defer src="/web/js/prism.js"></script>
         <!-- Global site tag (gtag.js) - Google Analytics -->
 			<script async src="https://www.googletagmanager.com/gtag/js?id=G-SW4FQEM9VM"></script>
@@ -42,7 +42,7 @@
 
     </head>
     <body>
-        <div class="goapp-app-info" id="app-wasm-layout">
+        <div id="app-wasm-layout" class="goapp-app-info">
             <img id="app-wasm-loader-icon" class="goapp-logo goapp-spin" src="https://storage.googleapis.com/murlok-github/icon-192.png">
             <p id="app-wasm-loader-label" class="goapp-label">
                 Loading go-app documentation...
diff --git a/docs/web/app.wasm b/docs/web/app.wasm
index 0375391b1..8a8f135ef 100755
Binary files a/docs/web/app.wasm and b/docs/web/app.wasm differ
diff --git a/docs/web/documents/js.md b/docs/web/documents/js.md
index e2d4ef821..7bb57c3f8 100644
--- a/docs/web/documents/js.md
+++ b/docs/web/documents/js.md
@@ -161,7 +161,7 @@ func (f *foo) Render() app.UI {
 }
 
 func (f *foo) onInputChange(ctx app.Context, e app.Event) {
-    v := ctx.JSSrc().Get("value").String()
+    v := ctx.JSSrc.Get("value").String()
 }
 ```
 
diff --git a/docs/web/documents/lifecycle.md b/docs/web/documents/lifecycle.md
new file mode 100644
index 000000000..85943e633
--- /dev/null
+++ b/docs/web/documents/lifecycle.md
@@ -0,0 +1,81 @@
+# Lifecycle
+
+![app load flow](/web/images/app-lifecycle.png)
+
+Apps created with this package are WebAssembly binaries that are served through HTTP requests.
+
+Because they are Progressive Web apps, they have to be available for offline mode. Under the hood, this is done by using [service workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) and caching mechanisms, which results in different app loading scenarios.
+
+### First loading
+
+App first loading is the app install. It occurs the first time a user goes on the app with a given web browser.
+
+1. Page is downloaded
+2. Service worker is downloaded
+3. App resources (app.wasm, CSS files and JS files) are downloaded
+4. Page, Service worker, and app resources are cached into the web browser
+
+### Recurrent loadings
+
+App recurrent loadings are occurring when a user is coming back on the app.
+
+1. Page is loaded from the web browser cache
+2. Service worker is downloaded
+3. **Service worker is compared with its cached version => They are identical**
+4. App resources are loaded from the web browser cache
+
+### Loading after app update
+
+App loading after an update is occurring when a user comes back on the app but the app has been modified since his/her last visit.
+
+1. Page is loaded from the web browser cache
+2. Service worker is downloaded
+3. **Service worker is compared with its cached version => They are different**
+4. Page and app resources are downloaded
+5. Page, Service worker, and app resources are cached into the web browser
+
+The trigger to update the app is a diff between the cached service worker and the live one. **Once the app is updated, the page still has to be reloaded** to be able to see the modifications since the current version displayed is the cached one.
+
+## Listen for updates
+
+Apps are automatically updated in the background when there are modifications.
+
+Since the displayed version of the app is the cached one, a common scenario is to visually notify the user that an update has been downloaded and that it is available by reloading the page.
+
+This can be done by implementing the [Updater](/reference#Updater) interface into a component:
+
+```go
+// A component that describes a UI.
+type littleApp struct {
+	app.Compo
+
+	// Field that reports whether an app update is available. False by default.
+	UpdateAvailable bool
+}
+
+// OnAppUpdate satisfies the app.Updater interface. It is called when the app is
+// updated in background.
+func (a *littleApp) OnAppUpdate(ctx app.Context) {
+	a.UpdateAvailable = true // Reports that an app update is available.
+	a.Update()               // Triggers UI update.
+}
+
+func (a *littleApp) Render() app.UI {
+	return app.Main().Body(
+		app.H1().Text("A little app"),
+		app.P().Text("That only display a text."),
+
+		// Displays an Update button when an update is available.
+		app.If(a.UpdateAvailable,
+			app.Button().
+				Text("Update!").
+				OnClick(a.onUpdateClick),
+		),
+	)
+}
+
+func (a *littleApp) onUpdateClick(ctx app.Context, e app.Event) {
+	// Reloads the page to display the modifications.
+	app.Reload()
+}
+```
diff --git a/docs/web/godoc-index.html b/docs/web/godoc-index.html
index cd7f8b97a..1d01a82c2 100644
--- a/docs/web/godoc-index.html
+++ b/docs/web/godoc-index.html
@@ -1115,6 +1115,11 @@
 				
 			
 				
+				<dd><a href="#Updater" id="src-Updater">type Updater</a></dd>
+				
+				
+			
+				
 				<dd><a href="#Value" id="src-Value">type Value</a></dd>
 				
 					
diff --git a/docs/web/godoc.html b/docs/web/godoc.html
index db0ab240e..26ffcd653 100644
--- a/docs/web/godoc.html
+++ b/docs/web/godoc.html
@@ -1193,6 +1193,11 @@ <h2 class="toggleButton" title="Click to hide Index section">Index ▾</h2>
 				
 			
 				
+				<dd><a href="#Updater" id="src-Updater" id="src-Updater">type Updater</a></dd>
+				
+				
+			
+				
 				<dd><a href="#Value" id="src-Value" id="src-Value">type Value</a></dd>
 				
 					
@@ -1744,7 +1749,7 @@ <h3 id="Window">func <a href="https://github.com/maxence-charriere/go-app/blob/m
 		
 			
 			
-			<h2 id="Compo">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L58">Compo</a>
+			<h2 id="Compo">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L65">Compo</a>
 				<a class="permalink" href="#Compo" id="src-Compo">¶</a>
 				
 				
@@ -1770,7 +1775,7 @@ <h2 id="Compo">type <a href="https://github.com/maxence-charriere/go-app/blob/ma
 
 			
 				
-				<h3 id="Compo.JSValue">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L72">JSValue</a>
+				<h3 id="Compo.JSValue">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L79">JSValue</a>
 					<a class="permalink" href="#Compo.JSValue" id="src-Compo.JSValue">¶</a>
 					
 					
@@ -1785,7 +1790,7 @@ <h3 id="Compo.JSValue">func (*Compo) <a href="https://github.com/maxence-charrie
 				
 			
 				
-				<h3 id="Compo.Kind">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L67">Kind</a>
+				<h3 id="Compo.Kind">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L74">Kind</a>
 					<a class="permalink" href="#Compo.Kind" id="src-Compo.Kind">¶</a>
 					
 					
@@ -1800,7 +1805,7 @@ <h3 id="Compo.Kind">func (*Compo) <a href="https://github.com/maxence-charriere/
 				
 			
 				
-				<h3 id="Compo.Mounted">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L77">Mounted</a>
+				<h3 id="Compo.Mounted">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L84">Mounted</a>
 					<a class="permalink" href="#Compo.Mounted" id="src-Compo.Mounted">¶</a>
 					
 					
@@ -1815,7 +1820,7 @@ <h3 id="Compo.Mounted">func (*Compo) <a href="https://github.com/maxence-charrie
 				
 			
 				
-				<h3 id="Compo.Render">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L86">Render</a>
+				<h3 id="Compo.Render">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L93">Render</a>
 					<a class="permalink" href="#Compo.Render" id="src-Compo.Render">¶</a>
 					
 					
@@ -1832,7 +1837,7 @@ <h3 id="Compo.Render">func (*Compo) <a href="https://github.com/maxence-charrier
 				
 			
 				
-				<h3 id="Compo.Update">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L106">Update</a>
+				<h3 id="Compo.Update">func (*Compo) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L113">Update</a>
 					<a class="permalink" href="#Compo.Update" id="src-Compo.Update">¶</a>
 					
 					
@@ -2122,7 +2127,7 @@ <h3 id="Event.PreventDefault">func (Event) <a href="https://github.com/maxence-c
 		
 			
 			
-			<h2 id="EventHandler">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L117">EventHandler</a>
+			<h2 id="EventHandler">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L118">EventHandler</a>
 				<a class="permalink" href="#EventHandler" id="src-EventHandler">¶</a>
 				
 				
@@ -22556,7 +22561,7 @@ <h2 id="Icon">type <a href="https://github.com/maxence-charriere/go-app/blob/mas
 		
 			
 			
-			<h2 id="Kind">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L31">Kind</a>
+			<h2 id="Kind">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L32">Kind</a>
 				<a class="permalink" href="#Kind" id="src-Kind">¶</a>
 				
 				
@@ -22601,7 +22606,7 @@ <h2 id="Kind">type <a href="https://github.com/maxence-charriere/go-app/blob/mas
 
 			
 				
-				<h3 id="Kind.String">func (Kind) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L33">String</a>
+				<h3 id="Kind.String">func (Kind) <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L34">String</a>
 					<a class="permalink" href="#Kind.String" id="src-Kind.String">¶</a>
 					
 					
@@ -23035,7 +23040,7 @@ <h2 id="UI">type <a href="https://github.com/maxence-charriere/go-app/blob/maste
 
 			
 				
-				<h3 id="FilterUIElems">func <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L82">FilterUIElems</a>
+				<h3 id="FilterUIElems">func <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/node.go#L83">FilterUIElems</a>
 					<a class="permalink" href="#FilterUIElems" id="src-FilterUIElems">¶</a>
 					
 					
@@ -23297,6 +23302,35 @@ <h3 id="Stack">func <a href="https://github.com/maxence-charriere/go-app/blob/ma
 		
 			
 			
+			<h2 id="Updater">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/component.go#L59">Updater</a>
+				<a class="permalink" href="#Updater" id="src-Updater">¶</a>
+				
+				
+			</h2>
+			<p>
+Updater is the interface that describes a component that can be notified when
+the app is updated.
+</p>
+
+			<pre>type Updater interface {
+    <span class="comment">// The function that is called when the application is updated.</span>
+    OnAppUpdate(<a href="#Context" id="src-Context">Context</a>)
+}</pre>
+
+			
+
+			
+
+			
+			
+			
+
+			
+
+			
+		
+			
+			
 			<h2 id="Value">type <a href="https://github.com/maxence-charriere/go-app/blob/master/pkg/app/js.go#L18">Value</a>
 				<a class="permalink" href="#Value" id="src-Value">¶</a>
 				
diff --git a/docs/web/images/app-lifecycle.png b/docs/web/images/app-lifecycle.png
new file mode 100644
index 000000000..e4556eaf4
Binary files /dev/null and b/docs/web/images/app-lifecycle.png differ
diff --git a/pkg/app/app_wasm.go b/pkg/app/app_wasm.go
index b18eb2921..e5eaabd4a 100644
--- a/pkg/app/app_wasm.go
+++ b/pkg/app/app_wasm.go
@@ -32,6 +32,10 @@ func run() {
 	initContent()
 	initContextMenu()
 
+	onappupdate := FuncOf(onAppUpdate)
+	defer onappupdate.Release()
+	Window().Set("goappOnUpdate", onappupdate)
+
 	onnav := FuncOf(onNavigate)
 	defer onnav.Release()
 	Window().Set("onclick", onnav)
@@ -121,6 +125,15 @@ func displayLoadError(err interface{}) {
 	loadingLabel.Set("innerText", fmt.Sprint(err))
 }
 
+func onAppUpdate(this Value, args []Value) interface{} {
+	dispatch(func() {
+		fmt.Println("app has been updated, reload to see changes")
+		content.onAppUpdate()
+	})
+
+	return nil
+}
+
 func onNavigate(this Value, args []Value) interface{} {
 	event := Event{Value: args[0]}
 	elem := event.Get("target")
diff --git a/pkg/app/component.go b/pkg/app/component.go
index 64b7f02ba..8fec7600d 100644
--- a/pkg/app/component.go
+++ b/pkg/app/component.go
@@ -64,6 +64,13 @@ type Navigator interface {
 	OnNav(Context, *url.URL)
 }
 
+// Updater is the interface that describes a component that can be notified when
+// the app is updated.
+type Updater interface {
+	// The function that is called when the application is updated.
+	OnAppUpdate(Context)
+}
+
 // Compo represents the base struct to use in order to build a component.
 type Compo struct {
 	ctx        context.Context
@@ -316,11 +323,22 @@ func (c *Compo) onNav(u *url.URL) {
 			Src:     c.self(),
 			JSSrc:   c.JSValue(),
 		}
-
 		nav.OnNav(ctx, u)
 	}
 }
 
+func (c *Compo) onAppUpdate() {
+	c.root.onAppUpdate()
+
+	if updater, ok := c.self().(Updater); ok {
+		updater.OnAppUpdate(Context{
+			Context: c.context(),
+			Src:     c.self(),
+			JSSrc:   c.JSValue(),
+		})
+	}
+}
+
 func (c *Compo) render() UI {
 	elems := FilterUIElems(c.this.Render())
 	return elems[0]
diff --git a/pkg/app/condition.go b/pkg/app/condition.go
index fa622997c..632a3d52b 100644
--- a/pkg/app/condition.go
+++ b/pkg/app/condition.go
@@ -119,3 +119,6 @@ func (c condition) update(UI) error {
 
 func (c condition) onNav(*url.URL) {
 }
+
+func (c condition) onAppUpdate() {
+}
diff --git a/pkg/app/element.go b/pkg/app/element.go
index fa320d068..df540115b 100644
--- a/pkg/app/element.go
+++ b/pkg/app/element.go
@@ -386,6 +386,12 @@ func (e *elem) onNav(u *url.URL) {
 	}
 }
 
+func (e *elem) onAppUpdate() {
+	for _, c := range e.children() {
+		c.onAppUpdate()
+	}
+}
+
 func (e *elem) html(w io.Writer) {
 	e.htmlWithIndent(w, 0)
 }
diff --git a/pkg/app/gen/app.js b/pkg/app/gen/app.js
index 9f4a1399c..b4f325379 100644
--- a/pkg/app/gen/app.js
+++ b/pkg/app/gen/app.js
@@ -1,11 +1,25 @@
 // -----------------------------------------------------------------------------
 // Init service worker
 // -----------------------------------------------------------------------------
+var goappOnUpdate = function () { };
+
+
 if ("serviceWorker" in navigator) {
   navigator.serviceWorker
     .register("{{.WorkerJS}}")
     .then(reg => {
       console.log("registering app service worker");
+
+      reg.onupdatefound = function () {
+        const installingWorker = reg.installing;
+        installingWorker.onstatechange = function () {
+          if (installingWorker.state == "installed") {
+            if (navigator.serviceWorker.controller) {
+              goappOnUpdate();
+            }
+          }
+        };
+      }
     })
     .catch(err => {
       console.error("offline service worker registration failed", err);
diff --git a/pkg/app/node.go b/pkg/app/node.go
index 90c252044..f376e745b 100644
--- a/pkg/app/node.go
+++ b/pkg/app/node.go
@@ -35,6 +35,7 @@ type UI interface {
 	dismount()
 	update(UI) error
 	onNav(*url.URL)
+	onAppUpdate()
 }
 
 // Kind represents the specific kind of a user interface element.
diff --git a/pkg/app/range.go b/pkg/app/range.go
index 1e7f1219d..9da55eb4c 100644
--- a/pkg/app/range.go
+++ b/pkg/app/range.go
@@ -149,3 +149,6 @@ func (r rangeLoop) update(UI) error {
 
 func (r rangeLoop) onNav(*url.URL) {
 }
+
+func (r rangeLoop) onAppUpdate() {
+}
diff --git a/pkg/app/raw.go b/pkg/app/raw.go
index f6d8ce4c4..dfc3bdd87 100644
--- a/pkg/app/raw.go
+++ b/pkg/app/raw.go
@@ -140,6 +140,9 @@ func (r *raw) update(n UI) error {
 func (r *raw) onNav(*url.URL) {
 }
 
+func (r *raw) onAppUpdate() {
+}
+
 func (r *raw) html(w io.Writer) {
 	r.htmlWithIndent(w, 0)
 }
diff --git a/pkg/app/scripts.go b/pkg/app/scripts.go
index 1c4120180..60adef92f 100644
--- a/pkg/app/scripts.go
+++ b/pkg/app/scripts.go
@@ -7,7 +7,7 @@ package app
 const (
 	wasmExecJS = "// Copyright 2018 The Go Authors. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n(() => {\n\t// Map multiple JavaScript environments to a single common API,\n\t// preferring web standards over Node.js API.\n\t//\n\t// Environments considered:\n\t// - Browsers\n\t// - Node.js\n\t// - Electron\n\t// - Parcel\n\n\tif (typeof global !== \"undefined\") {\n\t\t// global already exists\n\t} else if (typeof window !== \"undefined\") {\n\t\twindow.global = window;\n\t} else if (typeof self !== \"undefined\") {\n\t\tself.global = self;\n\t} else {\n\t\tthrow new Error(\"cannot export Go (neither global, window nor self is defined)\");\n\t}\n\n\tif (!global.require && typeof require !== \"undefined\") {\n\t\tglobal.require = require;\n\t}\n\n\tif (!global.fs && global.require) {\n\t\tconst fs = require(\"fs\");\n\t\tif (Object.keys(fs) !== 0) {\n\t\t\tglobal.fs = fs;\n\t\t}\n\t}\n\n\tconst enosys = () => {\n\t\tconst err = new Error(\"not implemented\");\n\t\terr.code = \"ENOSYS\";\n\t\treturn err;\n\t};\n\n\tif (!global.fs) {\n\t\tlet outputBuf = \"\";\n\t\tglobal.fs = {\n\t\t\tconstants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused\n\t\t\twriteSync(fd, buf) {\n\t\t\t\toutputBuf += decoder.decode(buf);\n\t\t\t\tconst nl = outputBuf.lastIndexOf(\"\\n\");\n\t\t\t\tif (nl != -1) {\n\t\t\t\t\tconsole.log(outputBuf.substr(0, nl));\n\t\t\t\t\toutputBuf = outputBuf.substr(nl + 1);\n\t\t\t\t}\n\t\t\t\treturn buf.length;\n\t\t\t},\n\t\t\twrite(fd, buf, offset, length, position, callback) {\n\t\t\t\tif (offset !== 0 || length !== buf.length || position !== null) {\n\t\t\t\t\tcallback(enosys());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst n = this.writeSync(fd, buf);\n\t\t\t\tcallback(null, n);\n\t\t\t},\n\t\t\tchmod(path, mode, callback) { callback(enosys()); },\n\t\t\tchown(path, uid, gid, callback) { callback(enosys()); },\n\t\t\tclose(fd, callback) { callback(enosys()); },\n\t\t\tfchmod(fd, mode, callback) { callback(enosys()); },\n\t\t\tfchown(fd, uid, gid, callback) { callback(enosys()); },\n\t\t\tfstat(fd, callback) { callback(enosys()); },\n\t\t\tfsync(fd, callback) { callback(null); },\n\t\t\tftruncate(fd, length, callback) { callback(enosys()); },\n\t\t\tlchown(path, uid, gid, callback) { callback(enosys()); },\n\t\t\tlink(path, link, callback) { callback(enosys()); },\n\t\t\tlstat(path, callback) { callback(enosys()); },\n\t\t\tmkdir(path, perm, callback) { callback(enosys()); },\n\t\t\topen(path, flags, mode, callback) { callback(enosys()); },\n\t\t\tread(fd, buffer, offset, length, position, callback) { callback(enosys()); },\n\t\t\treaddir(path, callback) { callback(enosys()); },\n\t\t\treadlink(path, callback) { callback(enosys()); },\n\t\t\trename(from, to, callback) { callback(enosys()); },\n\t\t\trmdir(path, callback) { callback(enosys()); },\n\t\t\tstat(path, callback) { callback(enosys()); },\n\t\t\tsymlink(path, link, callback) { callback(enosys()); },\n\t\t\ttruncate(path, length, callback) { callback(enosys()); },\n\t\t\tunlink(path, callback) { callback(enosys()); },\n\t\t\tutimes(path, atime, mtime, callback) { callback(enosys()); },\n\t\t};\n\t}\n\n\tif (!global.process) {\n\t\tglobal.process = {\n\t\t\tgetuid() { return -1; },\n\t\t\tgetgid() { return -1; },\n\t\t\tgeteuid() { return -1; },\n\t\t\tgetegid() { return -1; },\n\t\t\tgetgroups() { throw enosys(); },\n\t\t\tpid: -1,\n\t\t\tppid: -1,\n\t\t\tumask() { throw enosys(); },\n\t\t\tcwd() { throw enosys(); },\n\t\t\tchdir() { throw enosys(); },\n\t\t}\n\t}\n\n\tif (!global.crypto) {\n\t\tconst nodeCrypto = require(\"crypto\");\n\t\tglobal.crypto = {\n\t\t\tgetRandomValues(b) {\n\t\t\t\tnodeCrypto.randomFillSync(b);\n\t\t\t},\n\t\t};\n\t}\n\n\tif (!global.performance) {\n\t\tglobal.performance = {\n\t\t\tnow() {\n\t\t\t\tconst [sec, nsec] = process.hrtime();\n\t\t\t\treturn sec * 1000 + nsec / 1000000;\n\t\t\t},\n\t\t};\n\t}\n\n\tif (!global.TextEncoder) {\n\t\tglobal.TextEncoder = require(\"util\").TextEncoder;\n\t}\n\n\tif (!global.TextDecoder) {\n\t\tglobal.TextDecoder = require(\"util\").TextDecoder;\n\t}\n\n\t// End of polyfills for common API.\n\n\tconst encoder = new TextEncoder(\"utf-8\");\n\tconst decoder = new TextDecoder(\"utf-8\");\n\n\tglobal.Go = class {\n\t\tconstructor() {\n\t\t\tthis.argv = [\"js\"];\n\t\t\tthis.env = {};\n\t\t\tthis.exit = (code) => {\n\t\t\t\tif (code !== 0) {\n\t\t\t\t\tconsole.warn(\"exit code:\", code);\n\t\t\t\t}\n\t\t\t};\n\t\t\tthis._exitPromise = new Promise((resolve) => {\n\t\t\t\tthis._resolveExitPromise = resolve;\n\t\t\t});\n\t\t\tthis._pendingEvent = null;\n\t\t\tthis._scheduledTimeouts = new Map();\n\t\t\tthis._nextCallbackTimeoutID = 1;\n\n\t\t\tconst setInt64 = (addr, v) => {\n\t\t\t\tthis.mem.setUint32(addr + 0, v, true);\n\t\t\t\tthis.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);\n\t\t\t}\n\n\t\t\tconst getInt64 = (addr) => {\n\t\t\t\tconst low = this.mem.getUint32(addr + 0, true);\n\t\t\t\tconst high = this.mem.getInt32(addr + 4, true);\n\t\t\t\treturn low + high * 4294967296;\n\t\t\t}\n\n\t\t\tconst loadValue = (addr) => {\n\t\t\t\tconst f = this.mem.getFloat64(addr, true);\n\t\t\t\tif (f === 0) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t\tif (!isNaN(f)) {\n\t\t\t\t\treturn f;\n\t\t\t\t}\n\n\t\t\t\tconst id = this.mem.getUint32(addr, true);\n\t\t\t\treturn this._values[id];\n\t\t\t}\n\n\t\t\tconst storeValue = (addr, v) => {\n\t\t\t\tconst nanHead = 0x7FF80000;\n\n\t\t\t\tif (typeof v === \"number\" && v !== 0) {\n\t\t\t\t\tif (isNaN(v)) {\n\t\t\t\t\t\tthis.mem.setUint32(addr + 4, nanHead, true);\n\t\t\t\t\t\tthis.mem.setUint32(addr, 0, true);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tthis.mem.setFloat64(addr, v, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (v === undefined) {\n\t\t\t\t\tthis.mem.setFloat64(addr, 0, true);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet id = this._ids.get(v);\n\t\t\t\tif (id === undefined) {\n\t\t\t\t\tid = this._idPool.pop();\n\t\t\t\t\tif (id === undefined) {\n\t\t\t\t\t\tid = this._values.length;\n\t\t\t\t\t}\n\t\t\t\t\tthis._values[id] = v;\n\t\t\t\t\tthis._goRefCounts[id] = 0;\n\t\t\t\t\tthis._ids.set(v, id);\n\t\t\t\t}\n\t\t\t\tthis._goRefCounts[id]++;\n\t\t\t\tlet typeFlag = 0;\n\t\t\t\tswitch (typeof v) {\n\t\t\t\t\tcase \"object\":\n\t\t\t\t\t\tif (v !== null) {\n\t\t\t\t\t\t\ttypeFlag = 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"string\":\n\t\t\t\t\t\ttypeFlag = 2;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"symbol\":\n\t\t\t\t\t\ttypeFlag = 3;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"function\":\n\t\t\t\t\t\ttypeFlag = 4;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tthis.mem.setUint32(addr + 4, nanHead | typeFlag, true);\n\t\t\t\tthis.mem.setUint32(addr, id, true);\n\t\t\t}\n\n\t\t\tconst loadSlice = (addr) => {\n\t\t\t\tconst array = getInt64(addr + 0);\n\t\t\t\tconst len = getInt64(addr + 8);\n\t\t\t\treturn new Uint8Array(this._inst.exports.mem.buffer, array, len);\n\t\t\t}\n\n\t\t\tconst loadSliceOfValues = (addr) => {\n\t\t\t\tconst array = getInt64(addr + 0);\n\t\t\t\tconst len = getInt64(addr + 8);\n\t\t\t\tconst a = new Array(len);\n\t\t\t\tfor (let i = 0; i < len; i++) {\n\t\t\t\t\ta[i] = loadValue(array + i * 8);\n\t\t\t\t}\n\t\t\t\treturn a;\n\t\t\t}\n\n\t\t\tconst loadString = (addr) => {\n\t\t\t\tconst saddr = getInt64(addr + 0);\n\t\t\t\tconst len = getInt64(addr + 8);\n\t\t\t\treturn decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));\n\t\t\t}\n\n\t\t\tconst timeOrigin = Date.now() - performance.now();\n\t\t\tthis.importObject = {\n\t\t\t\tgo: {\n\t\t\t\t\t// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)\n\t\t\t\t\t// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported\n\t\t\t\t\t// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).\n\t\t\t\t\t// This changes the SP, thus we have to update the SP used by the imported function.\n\n\t\t\t\t\t// func wasmExit(code int32)\n\t\t\t\t\t\"runtime.wasmExit\": (sp) => {\n\t\t\t\t\t\tconst code = this.mem.getInt32(sp + 8, true);\n\t\t\t\t\t\tthis.exited = true;\n\t\t\t\t\t\tdelete this._inst;\n\t\t\t\t\t\tdelete this._values;\n\t\t\t\t\t\tdelete this._goRefCounts;\n\t\t\t\t\t\tdelete this._ids;\n\t\t\t\t\t\tdelete this._idPool;\n\t\t\t\t\t\tthis.exit(code);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)\n\t\t\t\t\t\"runtime.wasmWrite\": (sp) => {\n\t\t\t\t\t\tconst fd = getInt64(sp + 8);\n\t\t\t\t\t\tconst p = getInt64(sp + 16);\n\t\t\t\t\t\tconst n = this.mem.getInt32(sp + 24, true);\n\t\t\t\t\t\tfs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));\n\t\t\t\t\t},\n\n\t\t\t\t\t// func resetMemoryDataView()\n\t\t\t\t\t\"runtime.resetMemoryDataView\": (sp) => {\n\t\t\t\t\t\tthis.mem = new DataView(this._inst.exports.mem.buffer);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func nanotime1() int64\n\t\t\t\t\t\"runtime.nanotime1\": (sp) => {\n\t\t\t\t\t\tsetInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func walltime1() (sec int64, nsec int32)\n\t\t\t\t\t\"runtime.walltime1\": (sp) => {\n\t\t\t\t\t\tconst msec = (new Date).getTime();\n\t\t\t\t\t\tsetInt64(sp + 8, msec / 1000);\n\t\t\t\t\t\tthis.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func scheduleTimeoutEvent(delay int64) int32\n\t\t\t\t\t\"runtime.scheduleTimeoutEvent\": (sp) => {\n\t\t\t\t\t\tconst id = this._nextCallbackTimeoutID;\n\t\t\t\t\t\tthis._nextCallbackTimeoutID++;\n\t\t\t\t\t\tthis._scheduledTimeouts.set(id, setTimeout(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tthis._resume();\n\t\t\t\t\t\t\t\twhile (this._scheduledTimeouts.has(id)) {\n\t\t\t\t\t\t\t\t\t// for some reason Go failed to register the timeout event, log and try again\n\t\t\t\t\t\t\t\t\t// (temporary workaround for https://github.com/golang/go/issues/28975)\n\t\t\t\t\t\t\t\t\tconsole.warn(\"scheduleTimeoutEvent: missed timeout event\");\n\t\t\t\t\t\t\t\t\tthis._resume();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tgetInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early\n\t\t\t\t\t\t));\n\t\t\t\t\t\tthis.mem.setInt32(sp + 16, id, true);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func clearTimeoutEvent(id int32)\n\t\t\t\t\t\"runtime.clearTimeoutEvent\": (sp) => {\n\t\t\t\t\t\tconst id = this.mem.getInt32(sp + 8, true);\n\t\t\t\t\t\tclearTimeout(this._scheduledTimeouts.get(id));\n\t\t\t\t\t\tthis._scheduledTimeouts.delete(id);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func getRandomData(r []byte)\n\t\t\t\t\t\"runtime.getRandomData\": (sp) => {\n\t\t\t\t\t\tcrypto.getRandomValues(loadSlice(sp + 8));\n\t\t\t\t\t},\n\n\t\t\t\t\t// func finalizeRef(v ref)\n\t\t\t\t\t\"syscall/js.finalizeRef\": (sp) => {\n\t\t\t\t\t\tconst id = this.mem.getUint32(sp + 8, true);\n\t\t\t\t\t\tthis._goRefCounts[id]--;\n\t\t\t\t\t\tif (this._goRefCounts[id] === 0) {\n\t\t\t\t\t\t\tconst v = this._values[id];\n\t\t\t\t\t\t\tthis._values[id] = null;\n\t\t\t\t\t\t\tthis._ids.delete(v);\n\t\t\t\t\t\t\tthis._idPool.push(id);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\t// func stringVal(value string) ref\n\t\t\t\t\t\"syscall/js.stringVal\": (sp) => {\n\t\t\t\t\t\tstoreValue(sp + 24, loadString(sp + 8));\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueGet(v ref, p string) ref\n\t\t\t\t\t\"syscall/js.valueGet\": (sp) => {\n\t\t\t\t\t\tconst result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));\n\t\t\t\t\t\tsp = this._inst.exports.getsp(); // see comment above\n\t\t\t\t\t\tstoreValue(sp + 32, result);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueSet(v ref, p string, x ref)\n\t\t\t\t\t\"syscall/js.valueSet\": (sp) => {\n\t\t\t\t\t\tReflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueDelete(v ref, p string)\n\t\t\t\t\t\"syscall/js.valueDelete\": (sp) => {\n\t\t\t\t\t\tReflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueIndex(v ref, i int) ref\n\t\t\t\t\t\"syscall/js.valueIndex\": (sp) => {\n\t\t\t\t\t\tstoreValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));\n\t\t\t\t\t},\n\n\t\t\t\t\t// valueSetIndex(v ref, i int, x ref)\n\t\t\t\t\t\"syscall/js.valueSetIndex\": (sp) => {\n\t\t\t\t\t\tReflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueCall(v ref, m string, args []ref) (ref, bool)\n\t\t\t\t\t\"syscall/js.valueCall\": (sp) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst v = loadValue(sp + 8);\n\t\t\t\t\t\t\tconst m = Reflect.get(v, loadString(sp + 16));\n\t\t\t\t\t\t\tconst args = loadSliceOfValues(sp + 32);\n\t\t\t\t\t\t\tconst result = Reflect.apply(m, v, args);\n\t\t\t\t\t\t\tsp = this._inst.exports.getsp(); // see comment above\n\t\t\t\t\t\t\tstoreValue(sp + 56, result);\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 64, 1);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tstoreValue(sp + 56, err);\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 64, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueInvoke(v ref, args []ref) (ref, bool)\n\t\t\t\t\t\"syscall/js.valueInvoke\": (sp) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst v = loadValue(sp + 8);\n\t\t\t\t\t\t\tconst args = loadSliceOfValues(sp + 16);\n\t\t\t\t\t\t\tconst result = Reflect.apply(v, undefined, args);\n\t\t\t\t\t\t\tsp = this._inst.exports.getsp(); // see comment above\n\t\t\t\t\t\t\tstoreValue(sp + 40, result);\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 1);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tstoreValue(sp + 40, err);\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueNew(v ref, args []ref) (ref, bool)\n\t\t\t\t\t\"syscall/js.valueNew\": (sp) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst v = loadValue(sp + 8);\n\t\t\t\t\t\t\tconst args = loadSliceOfValues(sp + 16);\n\t\t\t\t\t\t\tconst result = Reflect.construct(v, args);\n\t\t\t\t\t\t\tsp = this._inst.exports.getsp(); // see comment above\n\t\t\t\t\t\t\tstoreValue(sp + 40, result);\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 1);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tstoreValue(sp + 40, err);\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueLength(v ref) int\n\t\t\t\t\t\"syscall/js.valueLength\": (sp) => {\n\t\t\t\t\t\tsetInt64(sp + 16, parseInt(loadValue(sp + 8).length));\n\t\t\t\t\t},\n\n\t\t\t\t\t// valuePrepareString(v ref) (ref, int)\n\t\t\t\t\t\"syscall/js.valuePrepareString\": (sp) => {\n\t\t\t\t\t\tconst str = encoder.encode(String(loadValue(sp + 8)));\n\t\t\t\t\t\tstoreValue(sp + 16, str);\n\t\t\t\t\t\tsetInt64(sp + 24, str.length);\n\t\t\t\t\t},\n\n\t\t\t\t\t// valueLoadString(v ref, b []byte)\n\t\t\t\t\t\"syscall/js.valueLoadString\": (sp) => {\n\t\t\t\t\t\tconst str = loadValue(sp + 8);\n\t\t\t\t\t\tloadSlice(sp + 16).set(str);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func valueInstanceOf(v ref, t ref) bool\n\t\t\t\t\t\"syscall/js.valueInstanceOf\": (sp) => {\n\t\t\t\t\t\tthis.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func copyBytesToGo(dst []byte, src ref) (int, bool)\n\t\t\t\t\t\"syscall/js.copyBytesToGo\": (sp) => {\n\t\t\t\t\t\tconst dst = loadSlice(sp + 8);\n\t\t\t\t\t\tconst src = loadValue(sp + 32);\n\t\t\t\t\t\tif (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 0);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst toCopy = src.subarray(0, dst.length);\n\t\t\t\t\t\tdst.set(toCopy);\n\t\t\t\t\t\tsetInt64(sp + 40, toCopy.length);\n\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 1);\n\t\t\t\t\t},\n\n\t\t\t\t\t// func copyBytesToJS(dst ref, src []byte) (int, bool)\n\t\t\t\t\t\"syscall/js.copyBytesToJS\": (sp) => {\n\t\t\t\t\t\tconst dst = loadValue(sp + 8);\n\t\t\t\t\t\tconst src = loadSlice(sp + 16);\n\t\t\t\t\t\tif (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {\n\t\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 0);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst toCopy = src.subarray(0, dst.length);\n\t\t\t\t\t\tdst.set(toCopy);\n\t\t\t\t\t\tsetInt64(sp + 40, toCopy.length);\n\t\t\t\t\t\tthis.mem.setUint8(sp + 48, 1);\n\t\t\t\t\t},\n\n\t\t\t\t\t\"debug\": (value) => {\n\t\t\t\t\t\tconsole.log(value);\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tasync run(instance) {\n\t\t\tthis._inst = instance;\n\t\t\tthis.mem = new DataView(this._inst.exports.mem.buffer);\n\t\t\tthis._values = [ // JS values that Go currently has references to, indexed by reference id\n\t\t\t\tNaN,\n\t\t\t\t0,\n\t\t\t\tnull,\n\t\t\t\ttrue,\n\t\t\t\tfalse,\n\t\t\t\tglobal,\n\t\t\t\tthis,\n\t\t\t];\n\t\t\tthis._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id\n\t\t\tthis._ids = new Map([ // mapping from JS values to reference ids\n\t\t\t\t[0, 1],\n\t\t\t\t[null, 2],\n\t\t\t\t[true, 3],\n\t\t\t\t[false, 4],\n\t\t\t\t[global, 5],\n\t\t\t\t[this, 6],\n\t\t\t]);\n\t\t\tthis._idPool = [];   // unused ids that have been garbage collected\n\t\t\tthis.exited = false; // whether the Go program has exited\n\n\t\t\t// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.\n\t\t\tlet offset = 4096;\n\n\t\t\tconst strPtr = (str) => {\n\t\t\t\tconst ptr = offset;\n\t\t\t\tconst bytes = encoder.encode(str + \"\\0\");\n\t\t\t\tnew Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);\n\t\t\t\toffset += bytes.length;\n\t\t\t\tif (offset % 8 !== 0) {\n\t\t\t\t\toffset += 8 - (offset % 8);\n\t\t\t\t}\n\t\t\t\treturn ptr;\n\t\t\t};\n\n\t\t\tconst argc = this.argv.length;\n\n\t\t\tconst argvPtrs = [];\n\t\t\tthis.argv.forEach((arg) => {\n\t\t\t\targvPtrs.push(strPtr(arg));\n\t\t\t});\n\t\t\targvPtrs.push(0);\n\n\t\t\tconst keys = Object.keys(this.env).sort();\n\t\t\tkeys.forEach((key) => {\n\t\t\t\targvPtrs.push(strPtr(`${key}=${this.env[key]}`));\n\t\t\t});\n\t\t\targvPtrs.push(0);\n\n\t\t\tconst argv = offset;\n\t\t\targvPtrs.forEach((ptr) => {\n\t\t\t\tthis.mem.setUint32(offset, ptr, true);\n\t\t\t\tthis.mem.setUint32(offset + 4, 0, true);\n\t\t\t\toffset += 8;\n\t\t\t});\n\n\t\t\tthis._inst.exports.run(argc, argv);\n\t\t\tif (this.exited) {\n\t\t\t\tthis._resolveExitPromise();\n\t\t\t}\n\t\t\tawait this._exitPromise;\n\t\t}\n\n\t\t_resume() {\n\t\t\tif (this.exited) {\n\t\t\t\tthrow new Error(\"Go program has already exited\");\n\t\t\t}\n\t\t\tthis._inst.exports.resume();\n\t\t\tif (this.exited) {\n\t\t\t\tthis._resolveExitPromise();\n\t\t\t}\n\t\t}\n\n\t\t_makeFuncWrapper(id) {\n\t\t\tconst go = this;\n\t\t\treturn function () {\n\t\t\t\tconst event = { id: id, this: this, args: arguments };\n\t\t\t\tgo._pendingEvent = event;\n\t\t\t\tgo._resume();\n\t\t\t\treturn event.result;\n\t\t\t};\n\t\t}\n\t}\n\n\tif (\n\t\tglobal.require &&\n\t\tglobal.require.main === module &&\n\t\tglobal.process &&\n\t\tglobal.process.versions &&\n\t\t!global.process.versions.electron\n\t) {\n\t\tif (process.argv.length < 3) {\n\t\t\tconsole.error(\"usage: go_js_wasm_exec [wasm binary] [arguments]\");\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\tconst go = new Go();\n\t\tgo.argv = process.argv.slice(2);\n\t\tgo.env = Object.assign({ TMPDIR: require(\"os\").tmpdir() }, process.env);\n\t\tgo.exit = process.exit;\n\t\tWebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {\n\t\t\tprocess.on(\"exit\", (code) => { // Node.js exits if no event handler is pending\n\t\t\t\tif (code === 0 && !go.exited) {\n\t\t\t\t\t// deadlock, make Go print error and stack traces\n\t\t\t\t\tgo._pendingEvent = { id: 0 };\n\t\t\t\t\tgo._resume();\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn go.run(result.instance);\n\t\t}).catch((err) => {\n\t\t\tconsole.error(err);\n\t\t\tprocess.exit(1);\n\t\t});\n\t}\n})();\n"
 
-	appJS = "// -----------------------------------------------------------------------------\n// Init service worker\n// -----------------------------------------------------------------------------\nif (\"serviceWorker\" in navigator) {\n  navigator.serviceWorker\n    .register(\"{{.WorkerJS}}\")\n    .then(reg => {\n      console.log(\"registering app service worker\");\n    })\n    .catch(err => {\n      console.error(\"offline service worker registration failed\", err);\n    });\n}\n\n// -----------------------------------------------------------------------------\n// Init progressive app\n// -----------------------------------------------------------------------------\nconst goappEnv = {{.Env }};\n\nfunction goappGetenv(k) {\n  return goappEnv[k];\n}\n\nlet deferredPrompt;\n\nwindow.addEventListener(\"beforeinstallprompt\", e => {\n  e.preventDefault();\n  deferredPrompt = e;\n});\n\n// -----------------------------------------------------------------------------\n// Init Web Assembly\n// -----------------------------------------------------------------------------\nif (!WebAssembly.instantiateStreaming) {\n  WebAssembly.instantiateStreaming = async (resp, importObject) => {\n    const source = await (await resp).arrayBuffer();\n    return await WebAssembly.instantiate(source, importObject);\n  };\n}\n\nconst go = new Go();\n\nWebAssembly.instantiateStreaming(fetch(\"{{.Wasm}}\"), go.importObject)\n  .then(result => {\n    const loaderIcon = document.getElementById(\"app-wasm-loader-icon\");\n    loaderIcon.className = \"goapp-logo\";\n\n    go.run(result.instance);\n  })\n  .catch(err => {\n    const loaderIcon = document.getElementById(\"app-wasm-loader-icon\");\n    loaderIcon.className = \"goapp-logo\";\n\n    const loaderLabel = document.getElementById(\"app-wasm-loader-label\");\n    loaderLabel.innerText = err;\n\n    console.error(\"loading wasm failed: \" + err);\n  });\n\n// -----------------------------------------------------------------------------\n// Keep body clean\n// -----------------------------------------------------------------------------\nfunction goappKeepBodyClean() {\n  const body = document.body;\n  const bodyChildrenCount = body.children.length;\n\n  const mutationObserver = new MutationObserver(function (mutationList) {\n    mutationList.forEach((mutation) => {\n      switch (mutation.type) {\n        case 'childList':\n          while (body.children.length > bodyChildrenCount) {\n            body.removeChild(body.lastChild);\n          }\n          break;\n      }\n    });\n  });\n\n  mutationObserver.observe(document.body, {\n    childList: true,\n  });\n\n  return () => mutationObserver.disconnect();\n}\n"
+	appJS = "// -----------------------------------------------------------------------------\n// Init service worker\n// -----------------------------------------------------------------------------\nvar goappOnUpdate = function () { };\n\n\nif (\"serviceWorker\" in navigator) {\n  navigator.serviceWorker\n    .register(\"{{.WorkerJS}}\")\n    .then(reg => {\n      console.log(\"registering app service worker\");\n\n      reg.onupdatefound = function () {\n        const installingWorker = reg.installing;\n        installingWorker.onstatechange = function () {\n          if (installingWorker.state == \"installed\") {\n            if (navigator.serviceWorker.controller) {\n              goappOnUpdate();\n            }\n          }\n        };\n      }\n    })\n    .catch(err => {\n      console.error(\"offline service worker registration failed\", err);\n    });\n}\n\n// -----------------------------------------------------------------------------\n// Init progressive app\n// -----------------------------------------------------------------------------\nconst goappEnv = {{.Env }};\n\nfunction goappGetenv(k) {\n  return goappEnv[k];\n}\n\nlet deferredPrompt;\n\nwindow.addEventListener(\"beforeinstallprompt\", e => {\n  e.preventDefault();\n  deferredPrompt = e;\n});\n\n// -----------------------------------------------------------------------------\n// Init Web Assembly\n// -----------------------------------------------------------------------------\nif (!WebAssembly.instantiateStreaming) {\n  WebAssembly.instantiateStreaming = async (resp, importObject) => {\n    const source = await (await resp).arrayBuffer();\n    return await WebAssembly.instantiate(source, importObject);\n  };\n}\n\nconst go = new Go();\n\nWebAssembly.instantiateStreaming(fetch(\"{{.Wasm}}\"), go.importObject)\n  .then(result => {\n    const loaderIcon = document.getElementById(\"app-wasm-loader-icon\");\n    loaderIcon.className = \"goapp-logo\";\n\n    go.run(result.instance);\n  })\n  .catch(err => {\n    const loaderIcon = document.getElementById(\"app-wasm-loader-icon\");\n    loaderIcon.className = \"goapp-logo\";\n\n    const loaderLabel = document.getElementById(\"app-wasm-loader-label\");\n    loaderLabel.innerText = err;\n\n    console.error(\"loading wasm failed: \" + err);\n  });\n\n// -----------------------------------------------------------------------------\n// Keep body clean\n// -----------------------------------------------------------------------------\nfunction goappKeepBodyClean() {\n  const body = document.body;\n  const bodyChildrenCount = body.children.length;\n\n  const mutationObserver = new MutationObserver(function (mutationList) {\n    mutationList.forEach((mutation) => {\n      switch (mutation.type) {\n        case 'childList':\n          while (body.children.length > bodyChildrenCount) {\n            body.removeChild(body.lastChild);\n          }\n          break;\n      }\n    });\n  });\n\n  mutationObserver.observe(document.body, {\n    childList: true,\n  });\n\n  return () => mutationObserver.disconnect();\n}\n"
 
 	appWorkerJS = "const cacheName = \"app-\" + \"{{.Version}}\";\n\nself.addEventListener(\"install\", event => {\n  console.log(\"installing app worker {{.Version}}\");\n  self.skipWaiting();\n\n  event.waitUntil(\n    caches.open(cacheName).then(cache => {\n      return cache.addAll([\n        {{range $path, $element := .ResourcesToCache}}\"{{$path}}\",\n        {{end}}\n      ]);\n    })\n  );\n});\n\nself.addEventListener(\"activate\", event => {\n  event.waitUntil(\n    caches.keys().then(keyList => {\n      return Promise.all(\n        keyList.map(key => {\n          if (key !== cacheName) {\n            return caches.delete(key);\n          }\n        })\n      );\n    })\n  );\n  console.log(\"app worker {{.Version}} is activated\");\n});\n\nself.addEventListener(\"fetch\", event => {\n  event.respondWith(\n    caches.match(event.request).then(response => {\n      return response || fetch(event.request);\n    })\n  );\n});\n"
 
diff --git a/pkg/app/text.go b/pkg/app/text.go
index 23f7b145d..66ffc3ac8 100644
--- a/pkg/app/text.go
+++ b/pkg/app/text.go
@@ -114,6 +114,9 @@ func (t *text) update(n UI) error {
 func (t *text) onNav(*url.URL) {
 }
 
+func (t *text) onAppUpdate() {
+}
+
 func (t *text) html(w io.Writer) {
 	t.htmlWithIndent(w, 0)
 }


Test patch:
diff --git a/pkg/app/component_test.go b/pkg/app/component_test.go
index 92c84f3be..f9c80adc5 100644
--- a/pkg/app/component_test.go
+++ b/pkg/app/component_test.go
@@ -275,11 +275,54 @@ func TestNestedInComponentNavigator(t *testing.T) {
 	require.Equal(t, "https://murlok.io", b.onNavURL)
 }
 
+func TestUpdater(t *testing.T) {
+	testSkipNonWasm(t)
+
+	h := &hello{}
+
+	err := mount(h)
+	require.NoError(t, err)
+	defer dismount(h)
+
+	h.onAppUpdate()
+	require.True(t, h.appUpdated)
+}
+
+func TestNestedUpdater(t *testing.T) {
+	testSkipNonWasm(t)
+
+	h := &hello{}
+	div := Div().Body(h)
+
+	err := mount(div)
+	require.NoError(t, err)
+	defer dismount(div)
+
+	div.onAppUpdate()
+	require.True(t, h.appUpdated)
+}
+
+func TestNestedInComponentUpdater(t *testing.T) {
+	testSkipNonWasm(t)
+
+	foo := &foo{Bar: "Bar"}
+
+	err := mount(foo)
+	require.NoError(t, err)
+	defer dismount(foo)
+
+	foo.onAppUpdate()
+
+	b := foo.children()[0].(*bar)
+	require.True(t, b.appUpdated)
+}
+
 type hello struct {
 	Compo
 
-	Greeting string
-	onNavURL string
+	Greeting   string
+	onNavURL   string
+	appUpdated bool
 }
 
 func (h *hello) OnMount(Context) {
@@ -289,6 +332,10 @@ func (h *hello) OnNav(ctx Context, u *url.URL) {
 	h.onNavURL = u.String()
 }
 
+func (h *hello) OnAppUpdate(ctx Context) {
+	h.appUpdated = true
+}
+
 func (h *hello) OnDismount(Context) {
 }
 
@@ -316,14 +363,19 @@ func (f *foo) Render() UI {
 
 type bar struct {
 	Compo
-	Value    string
-	onNavURL string
+	Value      string
+	onNavURL   string
+	appUpdated bool
 }
 
 func (b *bar) OnNav(ctx Context, u *url.URL) {
 	b.onNavURL = u.String()
 }
 
+func (b *bar) OnAppUpdate(ctx Context) {
+	b.appUpdated = true
+}
+
 func (b *bar) Render() UI {
 	return Text(b.Value)
 }