Repository: HapticX/happyx (language: python)

Issue:
Exception thrown on invalid value for Enum in model
An exception is thrown when an incorrect value is received for an enum parameter in a model. Don't know how to handle the exception

This code throws an exception when the POST is sent with an incorrect value in 'k'
```
import happyx

type
  Kind = enum
    circle
    square

model Obj:
  k: Kind
  st: string

serve("127.0.0.1", 5000):
  post "/[data:Obj:json]":
    echo data
    return ""
```

This is run with:
* happyx-3.9.0
* Nim devel
* OS: Archlinux

Exception thrown on invalid value for Enum in model
An exception is thrown when an incorrect value is received for an enum parameter in a model. Don't know how to handle the exception

This code throws an exception when the POST is sent with an incorrect value in 'k'
```
import happyx

type
  Kind = enum
    circle
    square

model Obj:
  k: Kind
  st: string

serve("127.0.0.1", 5000):
  post "/[data:Obj:json]":
    echo data
    return ""
```

This is run with:
* happyx-3.9.0
* Nim devel
* OS: Archlinux



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml
index ba9a33cbb..38ffde7bc 100644
--- a/.github/workflows/examples.yml
+++ b/.github/workflows/examples.yml
@@ -39,7 +39,7 @@ jobs:
         os:
           - ubuntu-latest
         nim_version:
-          - '2.0.0'
+          - '2.0.4'
     env:
       TIMEOUT_EXIT_STATUS: 124
     steps:
diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
index 3d81f412f..41a24e73e 100644
--- a/.github/workflows/gh-pages.yml
+++ b/.github/workflows/gh-pages.yml
@@ -30,7 +30,7 @@ jobs:
     runs-on: windows-latest
     needs: before
     env:
-      nim_version: '2.0.0'
+      nim_version: '2.0.4'
       node_version: '20'
     steps:
       - uses: actions/checkout@v4
diff --git a/.github/workflows/language_bindings.yml b/.github/workflows/language_bindings.yml
index 448248cb9..ac03a45b0 100644
--- a/.github/workflows/language_bindings.yml
+++ b/.github/workflows/language_bindings.yml
@@ -27,7 +27,7 @@ jobs:
         os:
           - ubuntu-latest
         nim_version:
-          - '2.0.0'
+          - '2.0.4'
     env:
       TIMEOUT_EXIT_STATUS: 124
     steps:
@@ -109,7 +109,7 @@ jobs:
         os:
           - ubuntu-latest
         nim_version:
-          - '2.0.0'
+          - '2.0.4'
         java_version:
           - '16'
         gradle_version:
diff --git a/bindings/java/src/main/resources/happyx.dll b/bindings/java/src/main/resources/happyx.dll
index f562ace22..24bbb41e9 100644
Binary files a/bindings/java/src/main/resources/happyx.dll and b/bindings/java/src/main/resources/happyx.dll differ
diff --git a/happyx.nimble b/happyx.nimble
index cf24b610f..be992980f 100755
--- a/happyx.nimble
+++ b/happyx.nimble
@@ -2,7 +2,7 @@
 
 description = "Macro-oriented asynchronous web-framework written with ♥"
 author = "HapticX"
-version = "3.9.0"
+version = "3.10.0"
 license = "MIT"
 srcDir = "src"
 installExt = @["nim"]
diff --git a/src/happyx/cli/flags_command.nim b/src/happyx/cli/flags_command.nim
new file mode 100644
index 000000000..785ff3c18
--- /dev/null
+++ b/src/happyx/cli/flags_command.nim
@@ -0,0 +1,42 @@
+import
+  ./utils
+
+
+proc flagsCommand*() =
+  ## Shows the general help message that describes
+  styledEcho "Here is ", fgMagenta, "HappyX", fgWhite, " flags:"
+  styledEcho " [Flags colors]:"
+  styledEcho fgMagenta, "   for SSG/SSR/SPA"
+  styledEcho fgCyan, "   flags with values"
+  styledEcho fgYellow, "   works with any flags"
+  styledEcho fgRed, "   experimental"
+  styledEcho " [Main]:"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "httpx", fgWhite, " - switches to HttpX server"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "beast", fgWhite, " - switches to HttpBeast server"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "micro", fgWhite, " - switches to MicroAsyncHttpServer server"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgRed, "translate", fgWhite, " - enables autotranslation (translatable needed)"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgCyan, "cryptoMethod", fgWhite, " - chooses crypto method"
+  styledEcho "     possible values: ", fgYellow, "sha224|sha256|sha384|sha512"
+  styledEcho "     default value: ", fgYellow, "sha512"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgCyan, "numThreads", fgWhite, " - chooses number of threads (for httpx/httpbeast)"
+  styledEcho "     default value: ", fgYellow, "0"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgCyan, "sessionIdLength", fgWhite, " - chooses length of session ID"
+  styledEcho "     default value: ", fgYellow, "32"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "disableApiDoc", fgWhite, " - disable API documentation (swagger and redoc)"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "appName", fgWhite, " - application name (for documentation)"
+  styledEcho "     default value: ", fgYellow, "\"HappyX Application\""
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "apiDocsPath", fgWhite, " - docs path (for documentation)"
+  styledEcho "     default value: ", fgYellow, "\"/docs\""
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "noliveviews", fgWhite, " - Disables LiveViews at SSR/SSG (It helpful for components)"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "safeRequests", fgWhite, " - Enables requests safety (On error returns 500 with err msg)"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgMagenta, "disableDefDeco", fgWhite, " - Disables default decorators (`AuthBasic`, `GetUserAgent`)"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgYellow, "debug", fgWhite, " - enables HappyX debug"
+  styledEcho " [DEV]:"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "compDebug", fgWhite, " - enables debug logging for components"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "compTreeDebug", fgWhite, " - enables debug logging for components (tree mode)"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "ssrDebug", fgWhite, " - enables debug logging for SSR"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "spaDebug", fgWhite, " - enables debug logging for SPA"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "reqModelDebug", fgWhite, " - enables debug logging for request models"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "routingDebug", fgWhite, " - enables debug logging for routing"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "componentDebugTarget", fgWhite, " - after this component program will terminated"
+  styledEcho "  ", fgYellow, "-d", fgWhite, ":", fgGreen, "reqModelDebugTarget", fgWhite, " - after this request model program will terminated"
diff --git a/src/happyx/cli/help_command.nim b/src/happyx/cli/help_command.nim
index 56922c394..11c0dcc26 100644
--- a/src/happyx/cli/help_command.nim
+++ b/src/happyx/cli/help_command.nim
@@ -10,7 +10,7 @@ proc mainHelpMessage*() =
   ## Shows the general help message that describes
   let
     subcommands = [
-      "build", "dev", "serve", "create", "html2tag", "update", "info", "help"
+      "build", "dev", "serve", "create", "html2tag", "update", "info", "flags", "help"
     ]
     logoColors = [
       fgBlue, fgGreen, fgYellow, fgWhite, fgCyan
diff --git a/src/happyx/cli/html2tag_command.nim b/src/happyx/cli/html2tag_command.nim
index cf78b885e..c4c774163 100644
--- a/src/happyx/cli/html2tag_command.nim
+++ b/src/happyx/cli/html2tag_command.nim
@@ -43,4 +43,4 @@ proc html2tagCommand*(output: string = "", args: seq[string]): int =
   file.close()
   styledEcho fgGreen, emoji["✅"](), " Successfully!"
   shutdownCli()
-  QuitSuccess
\ No newline at end of file
+  QuitSuccess
diff --git a/src/happyx/core/constants.nim b/src/happyx/core/constants.nim
index c705fe8a7..6a45a58eb 100755
--- a/src/happyx/core/constants.nim
+++ b/src/happyx/core/constants.nim
@@ -8,12 +8,10 @@
 ## | `-d:micro`           | enables MicroAsyncHttpServer as alternative HTTP Server ⚡    | ❌         |
 ## | `-d:translate`       | enables automatic translate for returns 🌐                     | ❌         |
 ## | `-d:debug`           | enables debug logging 💻                                      | ❌         |
-## | `-d:oldRenderer`     | enables old renderer for SPA 🍍                               | ❌         |
 ## | `-d:cryptoMethod`    | choose crypto method for `generate_password` methods 🔐       | ✅         |
 ## | `-d:numThreads`      | choose number of threads (httpx/httpbeast) ⌛                 |  ✅        |
 ## | `-d:sessionIdLength` | choose length of session ID ✍                                |  ✅        |
 ## | `-d:disableApiDoc`   | disables built-in API documentation 📕                        | ❌         |
-## | `-d:disableORM`      | disables built-in ORM 📑                                      | ❌         |
 ## | `-d:appName`         | choose name of application (SSR/SSG) 📕                       | ✅         |
 ## | `-d:apiDocsPath`     | choose path for API documentation 📕                          |  ✅        |
 ## | `-d:noliveviews`     | Disables LiveViews at SSR/SSG (It helpful for components) 📕  |  ❌        |
@@ -96,7 +94,7 @@ const
   nim_2_0_0* = (NimMajor, NimMinor, NimPatch) >= (2, 0, 0)
   # Framework version
   HpxMajor* = 3
-  HpxMinor* = 9
+  HpxMinor* = 10
   HpxPatch* = 0
   HpxVersion* = $HpxMajor & "." & $HpxMinor & "." & $HpxPatch
 
diff --git a/src/happyx/core/queries.nim b/src/happyx/core/queries.nim
index 6a9ebb33f..3e4455b3e 100644
--- a/src/happyx/core/queries.nim
+++ b/src/happyx/core/queries.nim
@@ -65,5 +65,5 @@ macro `?`*(strTable: StringTableRef | TableRef[string, seq[string]], key: untype
   ##      echo query?myParam
   ## 
   let
-    keyStr = newStrLitNode($key)
+    keyStr = newLit($key)
   newCall("getOrDefault", strTable, keyStr)
diff --git a/src/happyx/hpx.nim b/src/happyx/hpx.nim
index 5870e3aa7..ae2fb4779 100755
--- a/src/happyx/hpx.nim
+++ b/src/happyx/hpx.nim
@@ -10,7 +10,8 @@ import
     create_command, build_command,
     dev_command, serve_command,
     html2tag_command, help_command,
-    update_command, project_info_command
+    update_command, project_info_command,
+    flags_command
   ]
 
 import illwill except
@@ -25,6 +26,7 @@ proc html2tagCommandAux(output: string = "", args: seq[string]): int = html2tagC
 proc updateCommandAux(args: seq[string]): int = updateCommand(args)
 proc serveCommandAux(host: string = "0.0.0.0", port: int = 80): int = serveCommand(host, port)
 proc projectInfoCommandAux(): int = projectInfoCommand()
+proc flagsCommandAux(): int = flagsCommand()
 proc devCommandAux(host: string = "127.0.0.1", port: int = 5000,
                    reload: bool = false): int =
   devCommand(host, port, reload)
@@ -34,6 +36,7 @@ proc createCommandAux(name: string = "", kind: string = "", templates: bool = fa
 
 
 proc mainCommand(version = false): int =
+  ## HappyX main command
   if version:
     styledEcho "HappyX ", fgGreen, HpxVersion
   else:
@@ -52,6 +55,7 @@ when isMainModule:
     [createCommandAux, cmdName = "create"],
     [html2tagCommandAux, cmdName = "html2tag"],
     [updateCommandAux, cmdName = "update"],
+    [flagsCommandAux, cmdName = "flags"],
     [
       mainCommand,
       short = {"version": 'v'}
@@ -77,6 +81,8 @@ when isMainModule:
   case subcmd
   of "build":
     quit(dispatchbuild(cmdline = pars[1..^1]))
+  of "flags":
+    quit(dispatchflags(cmdline = pars[1..^1]))
   of "dev":
     quit(dispatchdev(cmdline = pars[1..^1]))
   of "serve":
@@ -152,6 +158,10 @@ when isMainModule:
       styledEcho "\nUsage:"
       styledEcho fgMagenta, "  hpx info"
       styledEcho fgYellow, "Use ", fgMagenta, "--no-emoji", fgYellow, " flag to disable emoji"
+    of "flags":
+      styledEcho fgBlue, "HappyX", fgMagenta, " flags ", fgWhite, "command displays all HappyX Nim flags."
+      styledEcho "\nUsage:"
+      styledEcho fgMagenta, "  hpx flags"
     else:
       styledEcho fgRed, "Unknown subcommand: ", fgWhite, subcmdHelp
     shutdownCli()
diff --git a/src/happyx/private/macro_utils.nim b/src/happyx/private/macro_utils.nim
index 0ff0ed0e8..787c87798 100644
--- a/src/happyx/private/macro_utils.nim
+++ b/src/happyx/private/macro_utils.nim
@@ -36,7 +36,7 @@ when not declared(macrocache.contains):
 proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
                          componentName: NimNode = newEmptyNode(), inCycle: bool = false,
                          cycleTmpVar: string = "", compTmpVar: NimNode = newEmptyNode(),
-                         cycleVars: var seq[NimNode]): NimNode
+                         cycleVars: var seq[NimNode], parent: NimNode = newEmptyNode()): NimNode
 
 
 proc bracket*(node: varargs[NimNode]): NimNode =
@@ -304,7 +304,7 @@ proc useComponent*(statement: NimNode, inCycle, inComponent: bool,
         newCall(
           "addArgIter",
           ident(componentData),
-          newCall("&", newStrLitNode"data-", newDotExpr(ident(componentName), ident(UniqueComponentId)))
+          newCall("&", newLit"data-", newDotExpr(ident(componentName), ident(UniqueComponentId)))
         )
       else:
         newEmptyNode(),
@@ -313,15 +313,15 @@ proc useComponent*(statement: NimNode, inCycle, inComponent: bool,
           newStmtList(
             newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
               ident"emit",
-              newStrLitNode(fmt"window.addEventListener('beforeunload', `{componentData}`.`exited`);")
+              newLit(fmt"window.addEventListener('beforeunload', `{componentData}`.`exited`);")
             )),
             newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
               ident"emit",
-              newStrLitNode(fmt"window.addEventListener('pagehide', `{componentData}`.`pageHide`);")
+              newLit(fmt"window.addEventListener('pagehide', `{componentData}`.`pageHide`);")
             )),
             newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
               ident"emit",
-              newStrLitNode(fmt"window.addEventListener('pageshow', `{componentData}`.`pageShow`);")
+              newLit(fmt"window.addEventListener('pageshow', `{componentData}`.`pageShow`);")
             )),
           )
         else:
@@ -421,7 +421,7 @@ proc attribute*(attr: NimNode, inComponent: bool = false): NimNode =
         else:
           attr[1]
     newColonExpr(
-      newStrLitNode(k),
+      newLit(k),
       formatNode(v)
     )
 
@@ -444,16 +444,29 @@ proc addAttribute*(node, key, value: NimNode, inComponent: bool = false) =
         formatNode(newLit($value[1] & "{self.uniqCompId}"))
       else:
         value
-  if node.len == 2:
-    node.add(newCall("newStringTable", newNimNode(nnkTableConstr).add(
-      newColonExpr(newStrLitNode(k), v)
-    )))
-  elif node[2].kind == nnkCall and node[2][0] == ident"newStringTable":
-    node[2][1].add(newColonExpr(newStrLitNode(k), v))
-  else:
-    node.insert(2, newCall("newStringTable", newNimNode(nnkTableConstr).add(
-      newColonExpr(newStrLitNode(k), v)
-    )))
+  if node.kind == nnkCall:
+    if node.len == 2:
+      node.add(newCall("newStringTable", newNimNode(nnkTableConstr).add(
+        newColonExpr(newLit(k), v)
+      )))
+    elif node[2].kind == nnkCall and node[2][0] == ident"newStringTable":
+      node[2][1].add(newColonExpr(newLit(k), v))
+    else:
+      node.insert(2, newCall("newStringTable", newNimNode(nnkTableConstr).add(
+        newColonExpr(newLit(k), v)
+      )))
+  elif node.kind == nnkStmtList and node[0].kind == nnkVarSection:
+    let n = node[0][0][2]
+    if n.len == 2:
+      n.add(newCall("newStringTable", newNimNode(nnkTableConstr).add(
+        newColonExpr(newLit(k), v)
+      )))
+    elif n[2].kind == nnkCall and n[2][0] == ident"newStringTable":
+      n[2][1].add(newColonExpr(newLit(k), v))
+    else:
+      n.insert(2, newCall("newStringTable", newNimNode(nnkTableConstr).add(
+        newColonExpr(newLit(k), v)
+      )))
 
 
 proc endsWithBuildHtml*(statement: NimNode): bool =
@@ -543,11 +556,13 @@ proc replaceSelfComponent*(statement, componentName: NimNode, parent: NimNode =
 proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
                          componentName: NimNode = newEmptyNode(), inCycle: bool = false,
                          cycleTmpVar: string = "", compTmpVar: NimNode = newEmptyNode(),
-                         cycleVars: var seq[NimNode]): NimNode =
+                         cycleVars: var seq[NimNode], parent: NimNode = newEmptyNode()): NimNode =
   ## Builds HTML
   ## 
   ## Here you can use components and event handlers
   let elementName = newLit(getTagName($root))
+  var events = newStmtList()
+  var elemEventId = uniqueId.value
   result = newCall("initTag", elementName)
 
   for statement in body:
@@ -572,7 +587,7 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
       # Real Nim code
       result.add(newStmtList(
         statement[1],
-        newCall("initTag", newStrLitNode"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
+        newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
       ))
     
     elif statement.kind == nnkCall and statement[0] == ident"rawHtml":
@@ -689,21 +704,21 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
                 newCall(
                   "addArgIter",
                   ident(componentData),
-                  newCall("&", newStrLitNode"data-", newDotExpr(compName, ident(UniqueComponentId)))
+                  newCall("&", newLit"data-", newDotExpr(compName, ident(UniqueComponentId)))
                 ),
                 when defined(js):
                   newStmtList(
                     newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
                       ident"emit",
-                      newStrLitNode(fmt"window.addEventListener('beforeunload', `{componentData}`.`exited`);")
+                      newLit(fmt"window.addEventListener('beforeunload', `{componentData}`.`exited`);")
                     )),
                     newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
                       ident"emit",
-                      newStrLitNode(fmt"window.addEventListener('pagehide', `{componentData}`.`pageHide`);")
+                      newLit(fmt"window.addEventListener('pagehide', `{componentData}`.`pageHide`);")
                     )),
                     newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
                       ident"emit",
-                      newStrLitNode(fmt"window.addEventListener('pageshow', `{componentData}`.`pageShow`);")
+                      newLit(fmt"window.addEventListener('pageshow', `{componentData}`.`pageShow`);")
                     )),
                   )
                 else:
@@ -741,21 +756,21 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
                 newCall(
                   "addArgIter",
                   ident(componentData),
-                  newCall("&", newStrLitNode"data-", newDotExpr(statement[1], ident(UniqueComponentId)))
+                  newCall("&", newLit"data-", newDotExpr(statement[1], ident(UniqueComponentId)))
                 ),
                 when defined(js):
                   newStmtList(
                     newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
                       ident"emit",
-                      newStrLitNode(fmt"window.addEventListener('beforeunload', `{componentData}`.`exited`);")
+                      newLit(fmt"window.addEventListener('beforeunload', `{componentData}`.`exited`);")
                     )),
                     newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
                       ident"emit",
-                      newStrLitNode(fmt"window.addEventListener('pagehide', `{componentData}`.`pageHide`);")
+                      newLit(fmt"window.addEventListener('pagehide', `{componentData}`.`pageHide`);")
                     )),
                     newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
                       ident"emit",
-                      newStrLitNode(fmt"window.addEventListener('pageshow', `{componentData}`.`pageShow`);")
+                      newLit(fmt"window.addEventListener('pageshow', `{componentData}`.`pageShow`);")
                     )),
                   )
                 else:
@@ -859,7 +874,8 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
       if inComponent:
         procedure.body = statement[^1]
         procedure.body.insert(0, newVarStmt(ident"self", newCall(componentName, ident"self")))
-        args.insert(1, newIdentDefs(ident"self", ident"BaseComponent"))
+        when not defined(js):
+          args.insert(1, newIdentDefs(ident"self", ident"BaseComponent"))
         # Detect in component and in cycle
         if inCycle:
           let
@@ -870,46 +886,64 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
           for i in cycleVars:
             procParams.add(newIdentDefs(i, ident"auto"))
             callRegister.add(i)
-          result.addAttribute(
-            newLit(evname),
-            newCall(
-              "fmt",
-              newStrLitNode(
-                "callComponentEventHandler('{self." & UniqueComponentId & "}', {-(" &
-                fmt"{uniqueId.value}" & cycleVar & ", event)"
+          when defined(js):
+            events.add(
+              newCall(
+                "addEventListener",
+                newDotExpr(ident("__el" & $elemEventId), ident"Element"),
+                newLit(event), procedure
               )
             )
-          )
-          result.add(
-            newStmtList(
-              newProc(ident(registerEvent), procParams, procedure),
+          else:
+            result.addAttribute(
+              newLit(evname),
               newCall(
-                "[]=",
-                ident"componentEventHandlers",
-                newCall("-", newCall("+", newIntLitNode(uniqueId.value), ident(cycleTmpVar))),
-                callRegister
-              ),
-              newCall("inc", ident(cycleTmpVar)),
-              newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
+                "fmt",
+                newLit(
+                  "callComponentEventHandler('{self." & UniqueComponentId & "}', {-(" &
+                  fmt"{uniqueId.value}" & cycleVar & ", event)"
+                )
+              )
+            )
+            result.add(
+              newStmtList(
+                newProc(ident(registerEvent), procParams, procedure),
+                newCall(
+                  "[]=",
+                  ident"componentEventHandlers",
+                  newCall("-", newCall("+", newLit(uniqueId.value), ident(cycleTmpVar))),
+                  callRegister
+                ),
+                newCall("inc", ident(cycleTmpVar)),
+                newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true)),
+              )
             )
-          )
         # In component and not in cycle
         else:
-          result.addAttribute(
-            newLit(evname),
-            newCall(
-              "fmt",
-              newLit(
-                "callComponentEventHandler('{self." & UniqueComponentId & "}', " & fmt"{uniqueId.value}, event)"
+          when defined(js):
+            events.add(
+              newCall(
+                "addEventListener",
+                newDotExpr(ident("__el" & $elemEventId), ident"Element"),
+                newLit(event), procedure
               )
             )
-          )
-          result.add(newStmtList(
-            newCall("once",
-              newCall("[]=", ident"componentEventHandlers", newIntLitNode(uniqueId.value), procedure)
-            ), newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
-          ))
-        procedure.body.insert(0, newAssignment(ident"currentComponent", newCall("fmt", newStrLitNode"{self.uniqCompId}")))
+          else:
+            result.addAttribute(
+              newLit(evname),
+              newCall(
+                "fmt",
+                newLit(
+                  "callComponentEventHandler('{self." & UniqueComponentId & "}', " & fmt"{uniqueId.value}, event)"
+                )
+              )
+            )
+            result.add(newStmtList(
+              newCall("once",
+                newCall("[]=", ident"componentEventHandlers", newLit(uniqueId.value), procedure)
+              ), newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
+            ))
+        procedure.body.insert(0, newAssignment(ident"currentComponent", newCall("fmt", newLit"{self.uniqCompId}")))
         procedure.body.add(newAssignment(ident"currentComponent", newLit""))
       else:
         procedure.body = statement[^1]
@@ -923,37 +957,60 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
           for i in cycleVars:
             procParams.add(newIdentDefs(i, ident"any"))
             callRegister.add(i)
-          result.addAttribute(
-            newStrLitNode(evname),
-            newCall(
-              "fmt",
-              newLit("callEventHandler({-(" & fmt"{uniqueId.value}" & cycleVar & ", event)")
+          when defined(js):
+            events.add(
+              newCall(
+                "addEventListener",
+                newDotExpr(ident("__el" & $elemEventId), ident"Element"),
+                newLit(event), procedure
+              )
             )
-          )
-          result.add(
-            newStmtList(
-              newProc(ident(registerEvent), procParams, procedure),
+          else:
+            result.addAttribute(
+              newLit(evname),
               newCall(
-                "[]=",
-                ident"eventHandlers",
-                newCall("-", newCall("+", newLit(uniqueId.value), ident(cycleTmpVar))),
-                callRegister
-              ),
-              newCall("inc", ident(cycleTmpVar)),
-              newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
+                "fmt",
+                newLit("callEventHandler({-(" & fmt"{uniqueId.value}" & cycleVar & ", event)")
+              )
+            )
+            result.add(
+              newStmtList(
+                newProc(ident(registerEvent), procParams, procedure),
+                newCall(
+                  "[]=",
+                  ident"eventHandlers",
+                  newCall("-", newCall("+", newLit(uniqueId.value), ident(cycleTmpVar))),
+                  callRegister
+                ),
+                newCall("inc", ident(cycleTmpVar)),
+                newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
+              )
             )
-          )
         # not in component and not in cycle
         else:
-          result.addAttribute(
-            newLit(evname),
-            newLit(fmt"callEventHandler({uniqueId.value}, event)")
-          )
-          result.add(newStmtList(
-            newCall("once",
-              newCall("[]=", ident"eventHandlers", newIntLitNode(uniqueId.value), procedure)
-            ), newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
-          ))
+          when defined(js):
+            events.add(
+              newCall(
+                "addEventListener",
+                newDotExpr(ident("__el" & $elemEventId), ident"Element"),
+                newLit(event), procedure
+              )
+            )
+          else:
+            result.addAttribute(
+              newLit(evname),
+              newLit(fmt"callEventHandler({uniqueId.value}, event)")
+            )
+            result.add(newStmtList(
+              newCall("once",
+                newCall("[]=", ident"eventHandlers", newLit(uniqueId.value), procedure)
+              ),
+              newCall("initTag", newLit"div", newCall("@", newNimNode(nnkBracket)), newLit(true))
+            ))
+      # echo result.toStrLit
+      # if events.len > 0:
+      # echo events.len
+      # echo "NEW EVENT: ", events[^1].toStrLit
       inc uniqueId
     
     elif statement.kind in {nnkIdent, nnkBracketExpr, nnkDotExpr} and statement notin [ident"@", ident":="]:
@@ -1201,3 +1258,12 @@ proc buildHtmlProcedure*(root, body: NimNode, inComponent: bool = false,
       for tag in tagRefs:
         arr.add(tag)
       result.add(newCall("@", arr))
+  if events.len > 0:
+    inc uniqueId, -1
+    result = newStmtList(
+      newVarStmt(ident("__el" & $elemEventId), result.copy())
+    )
+    for i in events:
+      result.add(i)
+    result.add(ident("__el" & $elemEventId))
+    inc uniqueId
diff --git a/src/happyx/routing/mounting.nim b/src/happyx/routing/mounting.nim
index 3a2db26bf..499cf9632 100644
--- a/src/happyx/routing/mounting.nim
+++ b/src/happyx/routing/mounting.nim
@@ -59,9 +59,9 @@ proc findAndReplaceMount*(body: NimNode) =
           # Replace routes
           if statement.kind in [nnkCall, nnkCommand]:
             if statement[0].kind in [nnkStrLit, nnkTripleStrLit]:
-              statement[0] = newStrLitNode($route & $statement[0])
+              statement[0] = newLit($route & $statement[0])
             elif statement[1].kind in [nnkStrLit, nnkTripleStrLit]:
-              statement[1] = newStrLitNode($route & $statement[1])
+              statement[1] = newLit($route & $statement[1])
           # Add mount routes
           if (statement.kind in [nnkCall, nnkCommand] and $statement[0] != "mount") or
              (statement.kind == nnkPrefix and $statement[0] == "@"):
diff --git a/src/happyx/routing/routing.nim b/src/happyx/routing/routing.nim
index 9331e5984..b22a52732 100755
--- a/src/happyx/routing/routing.nim
+++ b/src/happyx/routing/routing.nim
@@ -70,7 +70,7 @@ when exportPython:
     if re2"^[a-zA-Z][a-zA-Z0-9_]*$" notin name:
       raise newException(
         ValueError,
-        fmt"route param type name should be identifier (a-zA-Z0-0_), but got '{name}'"
+        fmt"route param type name should be identifier (a-zA-Z0-9_), but got '{name}'"
       )
     registeredRouteParamTypes[name] = RouteParamType(
       pattern: pattern, name: name, creator: creator
@@ -104,7 +104,7 @@ elif defined(napibuild):
     if re2"^[a-zA-Z][a-zA-Z0-9_]*$" notin name:
       raise newException(
         ValueError,
-        fmt"route param type name should be identifier (a-zA-Z0-0_), but got '{name}'"
+        fmt"route param type name should be identifier (a-zA-Z0-9_), but got '{name}'"
       )
     registeredRouteParamTypes[name] = RouteParamType(
       pattern: pattern, name: name, creator: creator
@@ -125,7 +125,7 @@ elif exportJvm:
     if re2"^[a-zA-Z][a-zA-Z0-9_]*$" notin name:
       raise newException(
         ValueError,
-        fmt"route param type name should be identifier (a-zA-Z0-0_), but got '{name}'"
+        fmt"route param type name should be identifier (a-zA-Z0-9_), but got '{name}'"
       )
     registeredRouteParamTypes[name] = RouteParamType(
       pattern: pattern, name: name, creator: creator
@@ -137,7 +137,7 @@ else:
     if re2"^[a-zA-Z][a-zA-Z0-9_]*$" notin $name:
       raise newException(
         ValueError,
-        fmt"route param type name should be identifier (a-zA-Z0-0_), but got '{name}'"
+        fmt"route param type name should be identifier (a-zA-Z0-9_), but got '{name}'"
       )
     registeredRouteParamTypes[$name] = newStmtList(name, pattern, creator)
 
@@ -152,22 +152,29 @@ proc newRequestModelObj*(name, typeName, target: string, mutable: bool): Request
 
 proc handleRoute*(route: string): RouteDataObj =
   ## Handles route and receive route data object.
+  ## 
+  ## ## Examples
+  ## 
+  ## dollar full: `$argument?:word[m]=hello`
+  ## curvy full: `{argument?:word[m]=hello}`
+  ## model full: `[argument:ModelName:json]`
+  ## 
   result = RouteDataObj(path: "", purePath: "", pathParams: @[], requestModels: @[])
   let
-    dollarToCurve = re2"\$([^:\/\{\}]+)(:enum\(\w+\)|:\w+|:\/[^\/]+\/)?(\[m\])?(=[^\/\{\}]+)?(m)?"
+    dollarToCurvy = re2"\$([^:\/\{\}]+)(:enum\(\w+\)|:\w+|:\/[^\/]+\/)?(\[m\])?(=[^\/\{\}]+)?(m)?"
     defaultWithoutQuestion = re2"\{([^:\/\{\}\?]+)(:enum\(\w+\)|:\w+|:\/[^\/]+\/)?(\[m\])?(=[^\}]+)\}"
-
   var path = route
   var m: RegexMatch2
-  if path.find(dollarToCurve, m):
+  if path.find(dollarToCurvy, m):
     if path[m.group(1)] == ":path":
       raise newException(ValueError, "path params doesn't support aliases")
     elif path[m.group(1)].startsWith(":/"):
       raise newException(ValueError, "regex params doesn't support aliases")
-  path = path.replace(dollarToCurve, "{$1$2$3$4}")
+  path = path.replace(dollarToCurvy, "{$1$2$3$4}")
   path = path.replace(defaultWithoutQuestion, "{$1?$2$3$4}")
   result.path = path
   var routePathStr = path
+  # Here we replace TYPENAME to regex pattern
   # boolean param
   routePathStr = routePathStr.replace(re2"\{[a-zA-Z][a-zA-Z0-9_]*(\??):bool(\[m\])?(=\S+?)?\}", "(n|y|no|yes|true|false|1|0|on|off)$1")
   # integer param
@@ -224,7 +231,7 @@ proc handleRoute*(route: string): RouteDataObj =
         path.findAll(
           re2"\[([a-zA-Z][a-zA-Z0-9_]*):([a-zA-Z][a-zA-Z0-9_]*)(\[m\])?(:[a-zA-Z\\-]+)?\]"
         )
-  
+
   result.purePath = routePathStr
 
   for pathParam in foundPathParams:
@@ -298,7 +305,7 @@ proc exportRouteArgs*(urlPath, routePath, body: NimNode): NimNode =
     hasChildren = false
   let
     elifBranch = newNimNode(nnkElifBranch)
-    regExp = newCall("re2", newStrLitNode("^" & routeData.purePath & "$"))
+    regExp = newCall("re2", newLit("^" & routeData.purePath & "$"))
   elifBranch.add(newCall("contains", urlPath, newStmtList(
     newLetStmt(ident"__regExp", regExp),
     ident"__regExp"
@@ -338,9 +345,9 @@ proc exportRouteArgs*(urlPath, routePath, body: NimNode): NimNode =
       foundGroup = newCall("decodeUrl", newNimNode(nnkBracketExpr).add(urlPath, group))
       foundGroupForce = newCall("decodeUrl", newNimNode(nnkBracketExpr).add(urlPath, groupForce))
       # _groupLen < 1
-      conditionOptional = newCall("<", newCall("len", group), newIntLitNode(1))
+      conditionOptional = newCall("<", newCall("len", group), newLit(1))
       # _foundGroupLen == 0
-      conditionSecondOptional = newCall("==", newCall("len", foundGroup), newIntLitNode(0))
+      conditionSecondOptional = newCall("==", newCall("len", foundGroup), newLit(0))
 
     if i.optional:
       case i.paramType:
@@ -510,9 +517,10 @@ proc exportRouteArgs*(urlPath, routePath, body: NimNode): NimNode =
               ident"JsonParsingError",
               newStmtList(
                 when enableDebug:
-                  newCall("echo", newCall("fmt", newStrLitNode"json parse error: {getCurrentExceptionMsg()}"))
+                  newCall("echo", newCall("fmt", newLit"json parse error: {getCurrentExceptionMsg()}"))
                 else:
                   newEmptyNode(),
+                newCall("__onException", urlPath, body, newCall"getCurrentException"),
                 newCall(
                   "answerJson",
                   ident"req",
@@ -526,9 +534,10 @@ proc exportRouteArgs*(urlPath, routePath, body: NimNode): NimNode =
               ident"JsonKindError",
               newStmtList(
                 when enableDebug:
-                  newCall("echo", newCall("fmt", newStrLitNode"json kind error: {getCurrentExceptionMsg()}"))
+                  newCall("echo", newCall("fmt", newLit"json kind error: {getCurrentExceptionMsg()}"))
                 else:
                   newEmptyNode(),
+                newCall("__onException", urlPath, body, newCall"getCurrentException"),
                 newCall(
                   "answerJson",
                   ident"req",
@@ -538,6 +547,23 @@ proc exportRouteArgs*(urlPath, routePath, body: NimNode): NimNode =
                 newNimNode(nnkReturnStmt).add(newEmptyNode()),
                 newCall("jsonTo" & i.typeName, newCall("newJObject"))
               )
+            ), newNimNode(nnkExceptBranch).add(
+              ident"Exception",
+              newStmtList(
+                when enableDebug:
+                  newCall("echo", newCall("fmt", newLit"json unknown error: {getCurrentExceptionMsg()}"))
+                else:
+                  newEmptyNode(),
+                newCall("__onException", urlPath, body, newCall"getCurrentException"),
+                newCall(
+                  "answerJson",
+                  ident"req",
+                  parseExpr"""{"response": "Unknown JSON structure"}""",
+                  ident"Http400"
+                ),
+                newNimNode(nnkReturnStmt).add(newEmptyNode()),
+                newCall("jsonTo" & i.typeName, newCall("newJObject"))
+              )
             ))
           of "urlencoded", "x-www-form-urlencoded", "xwwwformurlencoded":
             newCall("xWwwUrlencodedTo" & i.typeName, body)
diff --git a/src/happyx/spa/components.nim b/src/happyx/spa/components.nim
index 3c7f8cac8..f8899c557 100644
--- a/src/happyx/spa/components.nim
+++ b/src/happyx/spa/components.nim
@@ -9,6 +9,7 @@ import
   std/strformat,
   std/macros,
   std/macrocache,
+  std/htmlparser,
   std/os,
   # Thirdparty
   regex,
@@ -63,6 +64,15 @@ proc replaceSuperCall(statement, parentComponent: NimNode, funcName: string, nee
       replaceSuperCall(child, parentComponent, funcName, needDiscard, parentHasGenerics)
 
 
+when defined(js):
+  proc index(el: Element): int =
+    let children = el.parentNode.childNodes
+    for i in 0..children.len-1:
+      if children[i] == el:
+        return i
+    return -1
+
+
 template reRenderTmpl*() =
   let tmpData = "[data-" & self.uniqCompId & "]"
   when defined(js):
@@ -70,19 +80,45 @@ template reRenderTmpl*() =
   let compTmpData = self.render
   currentComponentsList.del(currentComponentsList.find(self.BaseComponent))
   compTmpData.addArgIter("data-" & self.uniqCompId)
+  echo tmpData
   when defined(js):
     var
       current = document.querySelector(tmpData)
-      elements = newSeq[Element]()
-    for tag in compTmpData.children:
-      if not current.isNil:
-        elements.add(current)
-        current = current.nextSibling.Element
-    for i in 0..<elements.len:
-      let
-        elem = elements[i]
-        tag = compTmpData.children[i]
-      elem.outerHTML = cstring($tag)
+      styleE = compTmpData.lastChild.cloneNode(true)
+      newE = current.cloneNode(true)
+      parent = current.parentElement
+      idx = index(current)
+      length = parent.children.len
+    echo idx, ", ", length
+    current.remove()
+    compTmpData.lastChild.remove()
+    if length-1 == idx:
+      parent.appendChild(compTmpData)
+    else:
+      parent.insertBefore(compTmpData, parent.children[idx])
+    compTmpData.appendChild(styleE)
+    # current.appendChild()
+    # current.innerHTML = ""
+    # current.appendChild()
+      # elements = newSeq[Element]()
+    # for tag in compTmpData.children:
+    #   if not current.isNil:
+    #     elements.add(current)
+    #     current = current.nextSibling.Element
+    # for i in 0..<elements.len:
+    #   let
+    #     elem = elements[i]
+    #     tag = compTmpData.children[i]
+    #     parent = elem.parentElement
+    #     idx = index(elem)
+    #     length = parent.children.len
+    #   echo idx, ", ", length
+    #   elem.remove()
+    #   if length-1 == idx:
+    #     parent.appendChild(tag)
+    #   else:
+    #     parent.insertBefore(tag, parent.children[idx])
+      # elem.outerHTML = cstring($tag)
     if activeElement.hasAttribute("id"):
       let actElem = document.getElementById(activeElement.id)
       if not actElem.isNil:
@@ -553,11 +589,16 @@ macro component*(name, body: untyped): untyped =
             )
           elif s[^1][0].kind == nnkCall and s[^1][0][0].kind == nnkIdent and $s[^1][0][0] == "buildStyle":
             # Pure CSS
-            let css = getAst(buildStyle(s[^1][0][1]))[1]
+            let
+              css = getAst(buildStyle(s[^1][0][1]))[1]
             styleStmtList = newStmtList(
               newAssignment(
                 ident"result",
-                newCall("fmt", newLit($css), newLit('<'), newLit('>'))
+                newCall("replace",
+                  newCall("fmt", newLit($css), newLit('<'), newLit('>')),
+                  newCall("re2", newLit("([\\S ]+?) *\\{")),
+                  newCall("fmt", newLit("$1[data-{self.uniqCompId}] {{"))
+                )
               )
             )
           else:
@@ -820,7 +861,7 @@ macro component*(name, body: untyped): untyped =
           )
         )
       elif extendsOf == "":
-        newStrLitNode("")
+        newLit("")
       elif extendsHasGenerics:
         newCall("procCall", newDotExpr(newCast(ident"self", extendsOfNode), ident"style"))
       else:
@@ -879,7 +920,7 @@ macro component*(name, body: untyped): untyped =
             )))
           ),
           afterStmtList,
-          newAssignment(ident"currentComponent", newStrLitNode("")),
+          newAssignment(ident"currentComponent", newLit("")),
         )
         when not defined(js):
           b = pragmaBlock([ident"gcsafe"], b)
@@ -995,7 +1036,11 @@ macro importComponent*(body: untyped): untyped =
   # Template
   if templateSource.len > 0:
     var
-      tagData =  initTag("div", @[tagFromString(componentData[templateSource[0].group(0)])], true)
+      tagData = 
+        when defined(js):
+          initTagVm("div", @[tagFromStringVm(componentData[templateSource[0].group(0)])], true)
+        else:
+          initTag("div", @[tagFromString(componentData[templateSource[0].group(0)])], true)
       statements = newStmtList()
     
     proc inCreatedComponents(tag: string): string =
@@ -1006,7 +1051,12 @@ macro importComponent*(body: untyped): untyped =
           return key
       ""
     
-    proc handle(tag: TagRef, parent: var NimNode) =
+    when defined(js):
+      discard
+    else:
+      type VmTagRef = TagRef
+    
+    proc handle(tag: VmTagRef, parent: var NimNode) =
       var ifStartIndex = -1
       for child in tag.children:
         # @click, @event, etc
@@ -1089,9 +1139,9 @@ macro importComponent*(body: untyped): untyped =
                   if child.name.toLower() == "script":
                     scriptLanguage = val
                   elif call.kind in [nnkElifBranch, nnkOfBranch, nnkElse, nnkForStmt, nnkWhileStmt]:
-                    call[^1][^1].add(newNimNode(nnkExprEqExpr).add(newStrLitNode(key), newStrLitNode(val)))
+                    call[^1][^1].add(newNimNode(nnkExprEqExpr).add(newLit(key), newLit(val)))
                   else:
-                    call.add(newNimNode(nnkExprEqExpr).add(newStrLitNode(key), newStrLitNode(val)))
+                    call.add(newNimNode(nnkExprEqExpr).add(newLit(key), newLit(val)))
                 else:
                   if call.kind in [nnkElifBranch, nnkOfBranch, nnkElse, nnkForStmt, nnkWhileStmt]:
                     if key.startsWith("h-on"):
@@ -1101,7 +1151,7 @@ macro importComponent*(body: untyped): untyped =
                         )
                       ))
                     else:
-                      call[^1][^1].add(newNimNode(nnkExprEqExpr).add(newStrLitNode(key), newStrLitNode(val)))
+                      call[^1][^1].add(newNimNode(nnkExprEqExpr).add(newLit(key), newLit(val)))
                   elif key.startsWith("h-on"):
                     eventHandlers.add(newCall(
                       newNimNode(nnkPrefix).add(ident"@", ident(key[4..^1])), ident"event", newStmtList(
@@ -1109,7 +1159,7 @@ macro importComponent*(body: untyped): untyped =
                       )
                     ))
                   else:
-                    call.add(newNimNode(nnkExprEqExpr).add(newStrLitNode(key), newStrLitNode(val)))
+                    call.add(newNimNode(nnkExprEqExpr).add(newLit(key), newLit(val)))
           elif name.len > 0:
             call = newNimNode(nnkCommand).add(ident"component", ident(name))
           else:
@@ -1163,7 +1213,7 @@ macro importComponent*(body: untyped): untyped =
       newNimNode(nnkAccQuoted).add(ident"script"),
       newStmtList(newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
         ident"emit",
-        newStrLitNode(componentData[scriptJSSource[0].group(0)])
+        newLit(componentData[scriptJSSource[0].group(0)])
       )))
     ))
   elif scriptNimSource.len > 0:
diff --git a/src/happyx/spa/renderer.nim b/src/happyx/spa/renderer.nim
index 3e2e365e8..2af7a9ea9 100644
--- a/src/happyx/spa/renderer.nim
+++ b/src/happyx/spa/renderer.nim
@@ -174,7 +174,7 @@ macro elem*(name: untyped): untyped =
       )),
       newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
         ident"emit",
-        newStrLitNode(fmt"`{uniqName}` = document.getElementById('{nameStr}');")
+        newLit(fmt"`{uniqName}` = document.getElementById('{nameStr}');")
       )),
       ident(uniqName)
     )
@@ -262,18 +262,9 @@ else:
 when defined(js):
   proc renderVdom*(app: App, tag: TagRef, force: bool = false) =
     ## Rerender DOM with VDOM
-    # compile with `-d:oldRenderer` to work with old renderer
-    when enableOldRenderer:
-      document.getElementById(app.appId).innerHTML = $tag
-    else:
-      let elem = document.getElementById(app.appId)
-      var
-        realDom = elem.Node
-        virtualDom = tag.toDom().n
-      # echo virtualDom.innerHTML
-      # echo realDom.innerHTML
-      # compareEdit(realDom, virtualDom)
-    realDom.innerHTML = virtualDom.innerHTML
+    var realDom = document.getElementById(app.appId).Node
+    realDom.innerHTML = ""
+    realDom.appendChild(tag)
     if force:
       for comp in createdComponentsList:
         comp.exited(comp, nil)
@@ -587,7 +578,7 @@ macro routes*(app: App, body: untyped): untyped =
 
   for key, val in sugarRoutes.pairs():
     if ($val[0]).toLower() in ["build", "page", "any"]:
-      body.add(newCall(newStrLitNode(key), val[1]))
+      body.add(newCall(newLit(key), val[1]))
   
   var
     cookiesInVar = newDotExpr(ident"document", ident"cookie")
@@ -719,14 +710,14 @@ macro routes*(app: App, body: untyped): untyped =
       newStmtList(
         newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
           ident"emit",
-          newStrLitNode(
+          newLit(
             "window.addEventListener('beforeunload', (e) => {"
           )
         )),
         finalize,
         newNimNode(nnkPragma).add(newNimNode(nnkExprColonExpr).add(
           ident"emit",
-          newStrLitNode(
+          newLit(
             "});"
           )
         )
diff --git a/src/happyx/spa/tag.nim b/src/happyx/spa/tag.nim
index d5c30898d..731aa8302 100644
--- a/src/happyx/spa/tag.nim
+++ b/src/happyx/spa/tag.nim
@@ -37,19 +37,29 @@ import
 
 
 when defined(js):
-  import dom
-
-
-type
-  TagRef* = ref Tag
-  Tag* = object
-    name*: string
-    parent*: TagRef
-    attrs*: StringTableRef
-    args*: seq[string]
-    children*: seq[TagRef]
-    isText*: bool  ## Ignore attributes and children when true
-    onlyChildren*: bool  ## Ignore self and shows only children
+  import std/dom
+
+  type
+    TagRef* = ref object of Element
+      onlyChildren*: bool  ## Ignore self and shows only children
+    VmTagRef* = ref object
+      name*: string
+      parent*: VmTagRef
+      attrs*: StringTableRef
+      args*: seq[string]
+      children*: seq[VmTagRef]
+      isText*: bool  ## Ignore attributes and children when true
+      onlyChildren*: bool  ## Ignore self and shows only children
+else:
+  type
+    TagRef* = ref object
+      name*: string
+      parent*: TagRef
+      attrs*: StringTableRef
+      args*: seq[string]
+      children*: seq[TagRef]
+      isText*: bool  ## Ignore attributes and children when true
+      onlyChildren*: bool  ## Ignore self and shows only children
 
 
 const
@@ -77,14 +87,24 @@ func add*(self: TagRef, tags: varargs[TagRef]) =
       child1 = tag"p"
       child2 = tag"p"
     rootTag.add(child1, child2)
-  for tag in tags:
-    if tag.isNil():
-      continue
-    self.children.add(tag)
-    tag.parent = self
+  when defined(js):
+    for tag in tags:
+      if tag.isNil():
+        continue
+      if tag.onlyChildren:
+        for i in tag.children:
+          self.appendChild(i)
+      else:
+        self.appendChild(tag)
+  else:
+    for tag in tags:
+      if tag.isNil():
+        continue
+      self.children.add(tag)
+      tag.parent = self
 
 
-func initTag*(name: string, attrs: StringTableRef,
+proc initTag*(name: string, attrs: StringTableRef,
               children: seq[TagRef] = @[],
               onlyChildren: bool = false): TagRef =
   ## Initializes a new HTML tag with the given name, attributes, and children.
@@ -96,16 +116,26 @@ func initTag*(name: string, attrs: StringTableRef,
   ## 
   ## Returns:
   ## - A reference to the newly created HTML tag.
-  result = TagRef(
-    name: name, isText: false, parent: nil,
-    attrs: attrs, children: @[], args: @[],
-    onlyChildren: onlyChildren
-  )
-  for child in children:
-    result.add(child)
+  when defined(js):
+    result = document.createElement(cstring name).TagRef
+    result.onlyChildren = onlyChildren
+    for key, val in attrs.pairs:
+      result.setAttribute(cstring(key), cstring(val))
+    for child in children:
+      if child.isNil:
+        continue
+      result.appendChild(child)
+  else:
+    result = TagRef(
+      name: name, isText: false, parent: nil,
+      attrs: attrs, children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
 
 
-func initTag*(name: string, children: seq[TagRef] = @[],
+proc initTag*(name: string, children: seq[TagRef] = @[],
               onlyChildren: bool = false): TagRef =
   ## Initializes a new HTML tag without attributes but with children
   ## 
@@ -115,16 +145,24 @@ func initTag*(name: string, children: seq[TagRef] = @[],
   ## 
   ## Returns:
   ## - A reference to the newly created HTML tag.
-  result = TagRef(
-    name: name, isText: false, parent: nil,
-    attrs: newStringTable(), children: @[], args: @[],
-    onlyChildren: onlyChildren
-  )
-  for child in children:
-    result.add(child)
+  when defined(js):
+    result = document.createElement(cstring(name)).TagRef
+    result.onlyChildren = onlyChildren
+    for child in children:
+      if child.isNil:
+        continue
+      result.appendChild(child)
+  else:
+    result = TagRef(
+      name: name, isText: false, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
 
 
-func initTag*(name: string, isText: bool, attrs: StringTableRef,
+proc initTag*(name: string, isText: bool, attrs: StringTableRef,
               children: seq[TagRef] = @[],
               onlyChildren: bool = false): TagRef =
   ## Initializes a new HTML tag with the given name, whether it's text or not, attributes, and children.
@@ -137,59 +175,113 @@ func initTag*(name: string, isText: bool, attrs: StringTableRef,
   ## 
   ## Returns:
   ## - A reference to the newly created HTML tag.
-  result = TagRef(
-    name: name, isText: isText, parent: nil,
-    attrs: attrs, children: @[], args: @[],
-    onlyChildren: onlyChildren
-  )
-  for child in children:
-    result.add(child)
+  when defined(js):
+    if isText:
+      result = document.createTextNode(cstring(name)).TagRef
+    else:
+      result = document.createElement(cstring(name)).TagRef
+      result.onlyChildren = onlyChildren
+      for key, val in attrs.pairs:
+        result.setAttribute(cstring(key), cstring(val))
+      for child in children:
+        if child.isNil:
+          continue
+        result.appendChild(child)
+  else:
+    result = TagRef(
+      name: name, isText: isText, parent: nil,
+      attrs: attrs, children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
 
 
-func initTag*(name: string, isText: bool, children: seq[TagRef] = @[],
+proc initTag*(name: string, isText: bool, children: seq[TagRef] = @[],
               onlyChildren: bool = false): TagRef =
   ## Initializes a new HTML tag
-  result = TagRef(
-    name: name, isText: isText, parent: nil,
-    attrs: newStringTable(), children: @[], args: @[],
-    onlyChildren: onlyChildren
-  )
-  for child in children:
-    result.add(child)
+  when defined(js):
+    if isText:
+      result = document.createTextNode(cstring(name)).TagRef
+    else:
+      result = document.createElement(cstring(name)).TagRef
+      result.onlyChildren = onlyChildren
+      for child in children:
+        if child.isNil:
+          continue
+        result.appendChild(child)
+  else:
+    result = TagRef(
+      name: name, isText: isText, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
 
 
-func tag*(name: string): TagRef {.inline.} =
+proc tag*(name: string): TagRef {.inline.} =
   ## Shortcut for `initTag func<#initTag,string,seq[TagRef]>`_
   runnableExamples:
     var root = tag"div"
-  TagRef(
-    name: name, isText: false, parent: nil,
-    attrs: newStringTable(), children: @[], args: @[],
-    onlyChildren: false
-  )
-
-
-func tag*(tag: TagRef): TagRef {.inline.} =
-  result = TagRef(
-    name: tag.name, isText: tag.isText, parent: tag.parent,
-    attrs: newStringTable(), children: @[],
-    onlyChildren: tag.onlyChildren, args: @[],
-  )
-  for child in tag.children:
-    result.add(child)
-  for k, v in tag.attrs.pairs:
-    result.attrs[k] = v
-
-
-func textTag*(text: string): TagRef {.inline.} =
+  when defined(js):
+    result = document.createElement(cstring(name)).TagRef
+    result.onlyChildren = false
+  else:
+    TagRef(
+      name: name, isText: false, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: false
+    )
+
+
+proc tag*(tag: TagRef): TagRef {.inline.} =
+  when defined(js):
+    result = tag.Element.cloneNode(true).Element.TagRef
+    result.onlyChildren = tag.onlyChildren
+  else:
+    result = TagRef(
+      name: tag.name, isText: tag.isText, parent: tag.parent,
+      attrs: newStringTable(), children: @[],
+      onlyChildren: tag.onlyChildren, args: @[],
+    )
+    for child in tag.children:
+      result.add(child)
+    for k, v in tag.attrs.pairs:
+      result.attrs[k] = v
+
+
+proc textTag*(text: string): TagRef {.inline.} =
   ## Shortcur for `initTag func<#initTag,string,bool,seq[TagRef],bool>`_
   runnableExamples:
     var root = textTag"Hello, world!"
-  TagRef(
-    name: text, isText: true, parent: nil,
-    attrs: newStringTable(), children: @[], args: @[],
-    onlyChildren: false
-  )
+  when defined(js):
+    result = document.createTextNode(cstring(text)).TagRef
+    result.onlyChildren = false
+  else:
+    TagRef(
+      name: text, isText: true, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: false
+    )
+
+
+when defined(js):
+  proc isText*(tag: TagRef): bool =
+    return tag.nodeType == NodeType.TextNode
+  
+  proc attrs*(tag: TagRef): seq[Node] =
+    return tag.attributes
+  
+  proc args*(tag: TagRef): seq[string] =
+    result = @[]
+    for i in tag.attributes.low..tag.attributes.high:
+      if $tag.attributes[i].nodeValue == "":
+        result.add($tag.attributes[i].nodeName)
+  
+  iterator pairs*(attrs: seq[Node]): tuple[key, val: string] =
+    for i in attrs.low..attrs.high:
+      yield (key: $attrs[i].nodeName, val: $attrs[i].nodeValue)
 
 
 proc xml2Tag(xml: XmlNode): TagRef =
@@ -198,10 +290,13 @@ proc xml2Tag(xml: XmlNode): TagRef =
     if xml.attrsLen > 0:
       result = initTag(xml.tag)
       for key, value in xml.attrs:
-        if value == "":
-          result.args.add(key)
+        when defined(js):
+          result.setAttribute(cstring(key), cstring(value))
         else:
-          result.attrs[key] = value
+          if value == "":
+            result.args.add(key)
+          else:
+            result.attrs[key] = value
     else:
       result = initTag(xml.tag)
   of xnText:
@@ -225,10 +320,10 @@ proc tagFromString*(source: string): TagRef {.inline.} =
   let xmlNode = parseHtml(source)
   result = initTag("div", @[], true)
   result.xmlTree2Tag(nil, xmlNode)
-  result = result.children[0].children[0]
+  result = result.children[0].children[0].TagRef
 
 
-func addArg*(self: TagRef, arg: string) =
+proc addArg*(self: TagRef, arg: string) =
   ## Adds arg into tag
   ## 
   ## ## Example
@@ -239,17 +334,24 @@ func addArg*(self: TagRef, arg: string) =
   ##    tag.addArg("data-1")
   ##    echo tag  # <div data-1></div>
   ## 
-  self.args.add(arg)
+  when defined(js):
+    self.setAttribute(cstring(arg), "")
+  else:
+    self.args.add(arg)
 
 
 proc addArgIter*(self: TagRef, arg: string) =
   ## Adds argument into current tag and all children
   ## 
   ## See also `addArg function #addArg,TagRef,string`_
-  if self.args.len == 0:
-    self.args.add(arg)
+  when defined(js):
+    if self.attributes.len == 0:
+      self.setAttribute(cstring(arg), "")
+  else:
+    if self.args.len == 0:
+      self.args.add(arg)
   for i in self.children:
-    i.addArgIter(arg)
+    i.TagRef.addArgIter(arg)
 
 
 proc toSeqIter*(self: TagRef): seq[TagRef] =
@@ -258,46 +360,49 @@ proc toSeqIter*(self: TagRef): seq[TagRef] =
   else:
     result = @[self]
   for child in self.children:
-    result = result.concat(child.toSeqIter)
+    result = result.concat(child.TagRef.toSeqIter)
   return result
 
 
 when defined(js):
   proc toDom*(self: TagRef): tuple[n: Node, b: bool] =
-    ## converts tag into DOM Element
-    if self.isText:
-      # detect text node
-      return (n: document.createTextNode(self.name), b: false)
-    elif self.onlyChildren:
-      # detect all children
-      var res = document.createElement("div")
-      # iter over all children
-      for child in self.children:
-        let dom = child.toDom()
-        if dom.b:
-          while dom.n.len > 0:
-            res.appendChild(dom.n.childNodes[0])
-        else:
-          res.appendChild(dom.n)
-      return (n: res, b: true)
-    # normal tag
-    var res = document.createElement(self.name)
-    # attributes
-    for key in self.attrs.keys():
-      res.setAttribute(key, self.attrs[key])
-    # args
-    for arg in self.args:
-      res.setAttribute(arg, "")
-    # children
-    for child in self.children:
-      let dom = child.toDom()
-      # only children
-      if dom.b:
-        while dom.n.len > 0:
-          res.appendChild(dom.n.childNodes[0])
-      else:
-        res.appendChild(dom.n)
-    return (n: res, b: false)
+    return (n: self.Node, b: false)
+# elif defined(js):
+#   proc toDom*(self: TagRef): tuple[n: Node, b: bool] =
+#     ## converts tag into DOM Element
+#     if self.isText:
+#       # detect text node
+#       return (n: document.createTextNode(self.name), b: false)
+#     elif self.onlyChildren:
+#       # detect all children
+#       var res = document.createElement("div")
+#       # iter over all children
+#       for child in self.children:
+#         let dom = child.toDom()
+#         if dom.b:
+#           while dom.n.len > 0:
+#             res.appendChild(dom.n.childNodes[0])
+#         else:
+#           res.appendChild(dom.n)
+#       return (n: res, b: true)
+#     # normal tag
+#     var res = document.createElement(self.name)
+#     # attributes
+#     for key in self.attrs.keys():
+#       res.setAttribute(key, self.attrs[key])
+#     # args
+#     for arg in self.args:
+#       res.setAttribute(arg, "")
+#     # children
+#     for child in self.children:
+#       let dom = child.toDom()
+#       # only children
+#       if dom.b:
+#         while dom.n.len > 0:
+#           res.appendChild(dom.n.childNodes[0])
+#       else:
+#         res.appendChild(dom.n)
+#     return (n: res, b: false)
 
 
 func lvl*(self: TagRef): int =
@@ -314,34 +419,52 @@ func lvl*(self: TagRef): int =
     assert root.lvl == 0
   result = 0
   var tag = self
-  while not isNil(tag.parent):
-    tag = tag.parent
-    if not tag.onlyChildren:
-      inc result
+  when defined(js):
+    while not tag.parentElement.isNil:
+      tag = tag.parentElement.TagRef
+      if not tag.onlyChildren:
+        inc result
+  else:
+    while not isNil(tag.parent):
+      tag = tag.parent
+      if not tag.onlyChildren:
+        inc result
 
 
 {. push inline .}
 
 func `[]`*(self: TagRef, attrName: string): string =
   ## Returns attribute by name
-  self.attrs[attrName]
+  when defined(js):
+    $self.getAttribute(cstring(attrName))
+  else:
+    self.attrs[attrName]
 
 func `[]`*(self: TagRef, index: int): TagRef =
   ## Returns tag by index
-  self.children[index]
+  self.children[index].TagRef
 
 
 func `[]=`*(self: TagRef, attrName: string, attrValue: string) =
   ## Sets a new value for attribute or create new attribute
-  self.attrs[attrName] = attrValue
+  when defined(js):
+    self.setAttribute(cstring(attrName), cstring(attrValue))
+  else:
+    self.attrs[attrName] = attrValue
 
 
 func getAttribute*(self: TagRef, attrName: string, default: string = ""): string =
   ## Returns attribute value if exists or default value if not.
-  if attrName in self.attrs:
-    self.attrs[attrName]
+  when defined(js):
+    if self.hasAttribute(cstring(attrName)):
+      $self.getAttribute(cstring(attrName))
+    else:
+      default
   else:
-    default
+    if attrName in self.attrs:
+      self.attrs[attrName]
+    else:
+      default
 
 {. pop .}
 
@@ -350,20 +473,34 @@ func findByTag*(self: TagRef, tag: string): seq[TagRef] =
   ## Finds all tags by name
   result = @[]
   for child in self.children:
-    if child.isText:
-      continue
-    for i in child.findByTag(tag):
-      result.add(i)
-    if child.name == tag:
-      result.add(child)
+    when defined(js):
+      if child.nodeType == NodeType.TextNode:
+        continue
+      for i in child.TagRef.findByTag(tag):
+        result.add(i)
+      if $child.nodeName == tag.toUpper():
+        result.add(child.TagRef)
+    else:
+      if child.isText:
+        continue
+      for i in child.findByTag(tag):
+        result.add(i)
+      if child.name == tag:
+        result.add(child)
 
 
 func get*(self: TagRef, tag: string): TagRef =
   ## Returns tag by name
-  for child in self.children:
-    if tag == child.name:
-      return child
-  raise newException(ValueError, fmt"<{self.name}> at level [{self.lvl}] doesn't have tag <{tag}>")
+  when defined(js):
+    for child in self.children:
+      if tag.toUpper() == $child.nodeName:
+        return child.TagRef
+    raise newException(ValueError, fmt"<{self.nodeName}> at level [{self.lvl}] doesn't have tag <{tag}>")
+  else:
+    for child in self.children:
+      if tag == child.name:
+        return child
+    raise newException(ValueError, fmt"<{self.name}> at level [{self.lvl}] doesn't have tag <{tag}>")
 
 
 func `$`*(self: TagRef): string =
@@ -371,28 +508,324 @@ func `$`*(self: TagRef): string =
   ## The function formats the tag with proper indentation based on the level of nesting
   ## and includes any attributes specified for the tag.
   ## If the tag is a text tag, the function simply returns the tag's name.
-  let
-    level = "  ".repeat(self.lvl)
-    argsStr = self.args.join(" ")
+  when defined(js):
+    return $self.outerHTML
+  else:
+    let
+      level = "  ".repeat(self.lvl)
+      argsStr = self.args.join(" ")
 
-  if self.isText:
-    return self.name
-  
-  var attrs = ""
-  for key, value in self.attrs.pairs():
-    if value.len > 0:
-      attrs &= " " & key & "=" & "\"" & value & "\""
+    if self.isText:
+      return self.name
+    
+    var attrs = ""
+    for key, value in self.attrs.pairs():
+      if value.len > 0:
+        attrs &= " " & key & "=" & "\"" & value & "\""
+      else:
+        attrs &= " " & key
+
+    if self.onlyChildren:
+      let children = self.children.join("\n")
+      return fmt"{level}{children}{level}"
+
+    if self.children.len > 0:
+      let children = "\n" & self.children.join("\n") & "\n"
+      fmt"{level}<{self.name}{attrs} {argsStr}>{children}{level}</{self.name}>"
+    elif self.name in UnclosedTags:
+      fmt"{level}<{self.name}{attrs} {argsStr}>"
     else:
-      attrs &= " " & key
+      fmt"{level}<{self.name}{attrs} {argsStr}></{self.name}>"
 
-  if self.onlyChildren:
-    let children = self.children.join("\n")
-    return fmt"{level}{children}{level}"
-
-  if self.children.len > 0:
-    let children = "\n" & self.children.join("\n") & "\n"
-    fmt"{level}<{self.name}{attrs} {argsStr}>{children}{level}</{self.name}>"
-  elif self.name in UnclosedTags:
-    fmt"{level}<{self.name}{attrs} {argsStr}>"
-  else:
-    fmt"{level}<{self.name}{attrs} {argsStr}></{self.name}>"
+
+when defined(js):
+  proc `$`*(self: Node): string =
+    $self.outerHTML
+
+  func add*(self: VmTagRef, tags: varargs[VmTagRef]) =
+    ## Adds `other` tag into `self` tag
+    runnableExamples:
+      var
+        rootTag = tag"div"
+        child1 = tag"p"
+        child2 = tag"p"
+      rootTag.add(child1, child2)
+    for tag in tags:
+      if tag.isNil():
+        continue
+      self.children.add(tag)
+      tag.parent = self
+
+  proc initTagVm*(name: string, attrs: StringTableRef,
+                children: seq[VmTagRef] = @[],
+                onlyChildren: bool = false): VmTagRef =
+    ## Initializes a new HTML tag with the given name, attributes, and children.
+    ## 
+    ## Args:
+    ## - `name`: The name of the HTML tag to create.
+    ## - `attrs`: The attributes to set for the HTML tag.
+    ## - `children`: The children to add to the HTML tag.
+    ## 
+    ## Returns:
+    ## - A reference to the newly created HTML tag.
+    result = VmTagRef(
+      name: name, isText: false, parent: nil,
+      attrs: attrs, children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
+
+  proc initTagVm*(name: string, children: seq[VmTagRef] = @[],
+                onlyChildren: bool = false): VmTagRef =
+    ## Initializes a new HTML tag without attributes but with children
+    ## 
+    ## Args:
+    ## - `name`: tag name
+    ## - `children`: The children to add to the HTML tag.
+    ## 
+    ## Returns:
+    ## - A reference to the newly created HTML tag.
+    result = VmTagRef(
+      name: name, isText: false, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
+
+
+  proc initTagVm*(name: string, isText: bool, attrs: StringTableRef,
+                children: seq[VmTagRef] = @[],
+                onlyChildren: bool = false): VmTagRef =
+    ## Initializes a new HTML tag with the given name, whether it's text or not, attributes, and children.
+    ## 
+    ## Args:
+    ## - `name`: The name of the HTML tag to create.
+    ## - `isText`: Whether the tag represents text.
+    ## - `attrs`: The attributes to set for the HTML tag.
+    ## - `children`: The children to add to the HTML tag.
+    ## 
+    ## Returns:
+    ## - A reference to the newly created HTML tag.
+    result = VmTagRef(
+      name: name, isText: isText, parent: nil,
+      attrs: attrs, children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
+
+
+  proc initTagVm*(name: string, isText: bool, children: seq[VmTagRef] = @[],
+                onlyChildren: bool = false): VmTagRef =
+    ## Initializes a new HTML tag
+    result = VmTagRef(
+      name: name, isText: isText, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: onlyChildren
+    )
+    for child in children:
+      result.add(child)
+
+
+  proc tagVm*(name: string): VmTagRef {.inline.} =
+    ## Shortcut for `initTag func<#initTag,string,seq[TagRef]>`_
+    runnableExamples:
+      var root = tag"div"
+    VmTagRef(
+      name: name, isText: false, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: false
+    )
+
+
+  proc tagVm*(tag: VmTagRef): VmTagRef {.inline.} =
+    result = VmTagRef(
+      name: tag.name, isText: tag.isText, parent: tag.parent,
+      attrs: newStringTable(), children: @[],
+      onlyChildren: tag.onlyChildren, args: @[],
+    )
+    for child in tag.children:
+      result.add(child)
+    for k, v in tag.attrs.pairs:
+      result.attrs[k] = v
+
+
+  proc textTagVm*(text: string): VmTagRef {.inline.} =
+    ## Shortcur for `initTag func<#initTag,string,bool,seq[TagRef],bool>`_
+    runnableExamples:
+      var root = textTag"Hello, world!"
+    VmTagRef(
+      name: text, isText: true, parent: nil,
+      attrs: newStringTable(), children: @[], args: @[],
+      onlyChildren: false
+    )
+
+
+  proc xml2TagVm(xml: XmlNode): VmTagRef =
+    case xml.kind
+    of xnElement:
+      if xml.attrsLen > 0:
+        result = initTagVm(xml.tag)
+        for key, value in xml.attrs:
+          if value == "":
+            result.args.add(key)
+          else:
+            result.attrs[key] = value
+      else:
+        result = initTagVm(xml.tag)
+    of xnText:
+      if re2"\A\s+\z" notin xml.text:
+        result = initTagVm(xml.text.replace(re2" +\z", ""), true)
+    else:
+      discard
+
+
+  proc xmlTree2Tag(current, parent: VmTagRef, tree: XmlNode) =
+    let tag = tree.xml2TagVm()
+    if not tag.isNil():
+      current.add(tag)
+    if tree.kind == xnElement:
+      for child in tree.items:
+        tag.xmlTree2Tag(current, child)
+
+
+  proc tagFromStringVm*(source: string): VmTagRef {.inline.} =
+    ## Translates raw HTML string into TagRef
+    let xmlNode = parseHtml(source)
+    result = initTagVm("div", @[], true)
+    result.xmlTree2Tag(nil, xmlNode)
+    result = result.children[0].children[0].VmTagRef
+
+
+  proc addArg*(self: VmTagRef, arg: string) =
+    ## Adds arg into tag
+    ## 
+    ## ## Example
+    ## 
+    ## .. code-block:: nim
+    ##    var tag = initTag("div")
+    ##    echo tag  # <div></div>
+    ##    tag.addArg("data-1")
+    ##    echo tag  # <div data-1></div>
+    ## 
+    self.args.add(arg)
+
+
+  proc addArgIter*(self: VmTagRef, arg: string) =
+    ## Adds argument into current tag and all children
+    ## 
+    ## See also `addArg function #addArg,TagRef,string`_
+    if self.args.len == 0:
+      self.args.add(arg)
+    for i in self.children:
+      i.VmTagRef.addArgIter(arg)
+
+
+  proc toSeqIter*(self: VmTagRef): seq[VmTagRef] =
+    if self.onlyChildren:
+      result = @[]
+    else:
+      result = @[self]
+    for child in self.children:
+      result = result.concat(child.VmTagRef.toSeqIter)
+    return result
+
+
+  func lvl*(self: VmTagRef): int =
+    ## This function returns the level of nesting of the current tag within its parent tags.
+    runnableExamples:
+      var
+        root = tag"div"
+        child1 = tag"h1"
+        child2 = tag"h2"
+      root.add(child1)
+      child1.add(child2)
+      assert child2.lvl == 2
+      assert child1.lvl == 1
+      assert root.lvl == 0
+    result = 0
+    var tag = self
+    while not isNil(tag.parent):
+      tag = tag.parent
+      if not tag.onlyChildren:
+        inc result
+
+
+  {. push inline .}
+
+  func `[]`*(self: VmTagRef, attrName: string): string =
+    ## Returns attribute by name
+    self.attrs[attrName]
+
+  func `[]`*(self: VmTagRef, index: int): VmTagRef =
+    ## Returns tag by index
+    self.children[index].VmTagRef
+
+
+  func `[]=`*(self: VmTagRef, attrName: string, attrValue: string) =
+    ## Sets a new value for attribute or create new attribute
+    self.attrs[attrName] = attrValue
+
+
+  func getAttribute*(self: VmTagRef, attrName: string, default: string = ""): string =
+    ## Returns attribute value if exists or default value if not.
+    if attrName in self.attrs:
+      self.attrs[attrName]
+    else:
+      default
+
+  {. pop .}
+
+
+  func findByTag*(self: VmTagRef, tag: string): seq[VmTagRef] =
+    ## Finds all tags by name
+    result = @[]
+    for child in self.children:
+      if child.isText:
+        continue
+      for i in child.findByTag(tag):
+        result.add(i)
+      if child.name == tag:
+        result.add(child)
+
+
+  func get*(self: VmTagRef, tag: string): VmTagRef =
+    ## Returns tag by name
+    for child in self.children:
+      if tag == child.name:
+        return child
+    raise newException(ValueError, fmt"<{self.name}> at level [{self.lvl}] doesn't have tag <{tag}>")
+
+
+  func `$`*(self: VmTagRef): string =
+    ## This function returns a string representation of the current tag and its child tags, if any.
+    ## The function formats the tag with proper indentation based on the level of nesting
+    ## and includes any attributes specified for the tag.
+    ## If the tag is a text tag, the function simply returns the tag's name.
+    let
+      level = "  ".repeat(self.lvl)
+      argsStr = self.args.join(" ")
+
+    if self.isText:
+      return self.name
+    
+    var attrs = ""
+    for key, value in self.attrs.pairs():
+      if value.len > 0:
+        attrs &= " " & key & "=" & "\"" & value & "\""
+      else:
+        attrs &= " " & key
+
+    if self.onlyChildren:
+      let children = self.children.join("\n")
+      return fmt"{level}{children}{level}"
+
+    if self.children.len > 0:
+      let children = "\n" & self.children.join("\n") & "\n"
+      fmt"{level}<{self.name}{attrs} {argsStr}>{children}{level}</{self.name}>"
+    elif self.name in UnclosedTags:
+      fmt"{level}<{self.name}{attrs} {argsStr}>"
+    else:
+      fmt"{level}<{self.name}{attrs} {argsStr}></{self.name}>"
diff --git a/src/happyx/spa/translatable.nim b/src/happyx/spa/translatable.nim
index 4450ee23e..f335f783e 100644
--- a/src/happyx/spa/translatable.nim
+++ b/src/happyx/spa/translatable.nim
@@ -81,7 +81,7 @@ macro translatable*(body: untyped): untyped =
         newAssignment(
           newNimNode(nnkBracketExpr).add(
             newNimNode(nnkBracketExpr).add(translations, source),
-            newStrLitNode"default"
+            newLit"default"
           ),
           source
         )
@@ -135,7 +135,7 @@ macro translate*(self: string): string =
       )
     source =
       when self is static[string]:
-        newStrLitNode(self)
+        newLit(self)
       else:
         self
     translations = ident"translates"
@@ -155,7 +155,7 @@ macro translate*(self: string): string =
         newNimNode(nnkBracketExpr).add(
           translations, source
         ),
-        newStrLitNode"default"
+        newLit"default"
       )
     ), newNimNode(nnkElse).add(
       newNimNode(nnkBracketExpr).add(
diff --git a/src/happyx/ssr/docs/autodocs.nim b/src/happyx/ssr/docs/autodocs.nim
index 20ee71c65..8329e57ea 100644
--- a/src/happyx/ssr/docs/autodocs.nim
+++ b/src/happyx/ssr/docs/autodocs.nim
@@ -27,9 +27,9 @@ proc fetchPathParams*(route: var string): tuple[pathParams, models: NimNode] =
   for i in routeData.pathParams:
     params.add(newCall(
       "newPathParamObj",
-      newStrLitNode(i.name),
-      newStrLitNode(i.paramType),
-      newStrLitNode(i.defaultValue),
+      newLit(i.name),
+      newLit(i.paramType),
+      newLit(i.defaultValue),
       newLit(i.optional),
       newLit(i.mutable),
     ))
@@ -37,9 +37,9 @@ proc fetchPathParams*(route: var string): tuple[pathParams, models: NimNode] =
   for i in routeData.requestModels:
     models.add(newCall(
       "newRequestModelObj",
-      newStrLitNode(i.name),
-      newStrLitNode(i.typeName),
-      newStrLitNode(i.target),
+      newLit(i.name),
+      newLit(i.typeName),
+      newLit(i.target),
       newLit(i.mutable),
     ))
   
@@ -62,7 +62,7 @@ proc fetchModelFields*(): NimNode =
     for field in val:
       tableConstr.add(newNimNode(nnkExprColonExpr).add(field[0], field[1]))
     if tableConstr.len > 0:
-      res.add(newNimNode(nnkExprColonExpr).add(newStrLitNode(key), newCall("newStringTable", tableConstr)))
+      res.add(newNimNode(nnkExprColonExpr).add(newLit(key), newCall("newStringTable", tableConstr)))
 
   if res.len > 0:
     newCall("toTable", res)
@@ -113,7 +113,7 @@ proc genApiDoc*(body: var NimNode): NimNode =
         ))
       
   # Get all documentation
-  body.add(newNimNode(nnkCommand).add(ident"get", newStrLitNode(
+  body.add(newNimNode(nnkCommand).add(ident"get", newLit(
     if apiDocsPath.startsWith("/"):
       apiDocsPath
     else:
@@ -122,7 +122,7 @@ proc genApiDoc*(body: var NimNode): NimNode =
     newCall("answerHtml", ident"req", newCall("renderDocsProcedure")),
   )))
   when not exportPython:
-    body.add(newNimNode(nnkCommand).add(ident"get", newStrLitNode(
+    body.add(newNimNode(nnkCommand).add(ident"get", newLit(
       if apiDocsPath.startsWith("/"):
         apiDocsPath & "/openapi.json"
       else:
@@ -225,7 +225,7 @@ proc procApiDocs*(docsData: NimNode): NimNode =
 proc happyxDocs*(docsData: NimNode): NimNode =
   ## Procedure that helps to generate docs route for HappyX
   procApiDocs(docsData).add(
-    newCall("compileTemplateStr", newStrLitNode(IndexApiDocPageTemplate))
+    newCall("compileTemplateStr", newLit(IndexApiDocPageTemplate))
   )
 
 
diff --git a/src/happyx/ssr/server.nim b/src/happyx/ssr/server.nim
index da36228dd..b99b5fd30 100755
--- a/src/happyx/ssr/server.nim
+++ b/src/happyx/ssr/server.nim
@@ -51,6 +51,7 @@
 import
   # Stdlib
   std/asyncdispatch,
+  std/asyncmacro,
   std/macrocache,
   std/strformat,
   std/asyncfile,
@@ -649,7 +650,7 @@ proc detectReturnStmt(node: NimNode, replaceReturn: bool = false) =
             )
           )
       # Really complete route after any return statement
-      node.insert(i+1, newNimNode(nnkReturnStmt).add(newEmptyNode()))
+      node.insert(i+1, newNimNode(nnkBreakStmt).add(ident"__handleRequestBlock"))
     else:
       node[i].detectReturnStmt(true)
   # Replace last node
@@ -677,7 +678,7 @@ proc detectReturnStmt(node: NimNode, replaceReturn: bool = false) =
     else:
       node[^1] = newCall("answer", ident"req", node[^1])
   # Really complete route after any return statement
-  node.add(newNimNode(nnkReturnStmt).add(newEmptyNode()))
+  node.add(newNimNode(nnkBreakStmt).add(ident"__handleRequestBlock"))
 
 
 macro routes*(server: Server, body: untyped = newStmtList()): untyped =
@@ -748,6 +749,7 @@ macro routes*(server: Server, body: untyped = newStmtList()): untyped =
     stmtList = newStmtList()
     staticDirs: seq[NimNode]
     notFoundNode = newEmptyNode()
+    onException = newStmtList()
     procStmt = newProc(
       ident"handleRequest",
       [
@@ -756,7 +758,10 @@ macro routes*(server: Server, body: untyped = newStmtList()): untyped =
       ],
       when enableSafeRequests:
         newNimNode(nnkTryStmt).add(
-          stmtList,
+          newNimNode(nnkBlockStmt).add(
+            ident"__handleRequestBlock",
+            stmtList,
+          ),
           newNimNode(nnkExceptBranch).add(
             newCall(
               ident"answer", ident"req",
@@ -766,7 +771,10 @@ macro routes*(server: Server, body: untyped = newStmtList()): untyped =
           )
         )
       else:
-        stmtList,
+        newNimNode(nnkBlockStmt).add(
+          ident"__handleRequestBlock",
+          stmtList,
+        ),
     )
     caseRequestMethodsStmt = newNimNode(nnkCaseStmt)
     methodTable = newTable[string, NimNode]()
@@ -891,8 +899,8 @@ socketToSsr.onmessage=function(m){
 
   when enableHttpx or enableHttpBeast:
     var path = newNimNode(nnkBracketExpr).add(
-      newCall("split", newCall("get", newCall("path", ident"req")), newStrLitNode"?"),
-      newIntLitNode(0)
+      newCall("split", newCall("get", newCall("path", ident"req")), newLit"?"),
+      newLit(0)
     )
     let
       reqMethod = newCall("get", newDotExpr(ident"req", ident"httpMethod"))
@@ -900,18 +908,18 @@ socketToSsr.onmessage=function(m){
       headers = newCall("get", newDotExpr(ident"req", ident"headers"))
       acceptLanguage = newNimNode(nnkBracketExpr).add(
         newCall(
-          "split", newNimNode(nnkBracketExpr).add(headers, newStrLitNode"accept-language"), newLit(',')
+          "split", newNimNode(nnkBracketExpr).add(headers, newLit"accept-language"), newLit(',')
         ), newLit(0)
       )
       val = ident(fmt"_val")
       url = newStmtList(
-        newLetStmt(val, newCall("split", newCall("get", newCall("path", ident"req")), newStrLitNode"?")),
+        newLetStmt(val, newCall("split", newCall("get", newCall("path", ident"req")), newLit"?")),
         newNimNode(nnkIfStmt).add(
           newNimNode(nnkElifBranch).add(
-            newCall(">=", newCall("len", val), newIntLitNode(2)),
-            newNimNode(nnkBracketExpr).add(val, newIntLitNode(1))
+            newCall(">=", newCall("len", val), newLit(2)),
+            newNimNode(nnkBracketExpr).add(val, newLit(1))
           ), newNimNode(nnkElse).add(
-            newStrLitNode("")
+            newLit("")
           )
         )
       )
@@ -923,23 +931,23 @@ socketToSsr.onmessage=function(m){
       headers = newDotExpr(ident"req", ident"headers")
       acceptLanguage = newNimNode(nnkBracketExpr).add(
         newCall(
-          "split", newNimNode(nnkBracketExpr).add(headers, newStrLitNode"accept-language"), newLit(',')
+          "split", newNimNode(nnkBracketExpr).add(headers, newLit"accept-language"), newLit(',')
         ), newLit(0)
       )
       url = newDotExpr(newDotExpr(ident"req", ident"url"), ident"query")
   let
     directoryFromPath = newCall(
       "&",
-      newStrLitNode".",
+      newLit".",
       newCall("replace", pathIdent, newLit('/'), ident"DirSep")
     )
     cookiesOutVar = newCall(newNimNode(nnkBracketExpr).add(ident"newSeq", ident"string"))
     cookiesInVar = newNimNode(nnkIfStmt).add(
       newNimNode(nnkElifBranch).add(
-        newCall("hasKey", headers, newStrLitNode"cookie"),
-        newCall("parseCookies", newCall("$", newNimNode(nnkBracketExpr).add(headers, newStrLitNode"cookie")))
+        newCall("hasKey", headers, newLit"cookie"),
+        newCall("parseCookies", newCall("$", newNimNode(nnkBracketExpr).add(headers, newLit"cookie")))
       ), newNimNode(nnkElse).add(
-        newCall("parseCookies", newStrLitNode(""))
+        newCall("parseCookies", newLit(""))
       )
     )
     isWebsocketConnection =
@@ -947,13 +955,13 @@ socketToSsr.onmessage=function(m){
         "and",
         newCall(
           "and",
-          newCall("hasKey", headers, newStrLitNode"connection"),
-          newCall("hasKey", headers, newStrLitNode"upgrade"),
+          newCall("hasKey", headers, newLit"connection"),
+          newCall("hasKey", headers, newLit"upgrade"),
         ),
         newCall(
           "and",
-          newCall("contains", newCall("[]", headers, newStrLitNode"connection"), newStrLitNode"upgrade"),
-          newCall("==", newCall("toLower", newCall("[]", headers, newStrLitNode"upgrade", newLit(0))), newStrLitNode"websocket"),
+          newCall("contains", newCall("[]", headers, newLit"connection"), newLit"upgrade"),
+          newCall("==", newCall("toLower", newCall("[]", headers, newLit"upgrade", newLit(0))), newLit"websocket"),
         )
       )
     wsClientI = ident"wsClient"
@@ -971,11 +979,11 @@ socketToSsr.onmessage=function(m){
 
   for key, val in sugarRoutes.pairs():
     if ($val[0]).toLower() == "any":
-      body.add(newCall(newStrLitNode(key), val[1]))
+      body.add(newCall(newLit(key), val[1]))
     elif ($val[0]).toLower() in httpMethods:
       body.add(newNimNode(nnkCommand).add(
         val[0],
-        newStrLitNode(key),
+        newLit(key),
         val[1]
       ))
   
@@ -1335,7 +1343,7 @@ socketToSsr.onmessage=function(m){
                         newStmtList(
                           when enableDebug:
                             newStmtList(
-                              newCall("echo", newStrLitNode"Socket closed"),
+                              newCall("echo", newLit"Socket closed"),
                               wsDelStmt,
                               newCall("__wsClosed", wsClientI)
                             )
@@ -1357,7 +1365,7 @@ socketToSsr.onmessage=function(m){
                         newStmtList(
                           newCall(
                             "echo",
-                            newCall("fmt", newStrLitNode"Unexpected socket error: {getCurrentExceptionMsg()}")
+                            newCall("fmt", newLit"Unexpected socket error: {getCurrentExceptionMsg()}")
                           ),
                           wsDelStmt,
                           newCall("__wsError", wsClientI)
@@ -1392,7 +1400,7 @@ socketToSsr.onmessage=function(m){
                   when enableDebug:
                     newStmtList(
                       newCall(
-                        "echo", newCall("fmt", newStrLitNode"Socket closed: {getCurrentExceptionMsg()}")
+                        "echo", newCall("fmt", newLit"Socket closed: {getCurrentExceptionMsg()}")
                       ),
                       wsDelStmt,
                       newCall("__wsClosed", wsClientI)
@@ -1409,7 +1417,7 @@ socketToSsr.onmessage=function(m){
                     newStmtList(
                       newCall(
                         "echo",
-                        newCall("fmt", newStrLitNode"Socket tried to use an unknown protocol: {getCurrentExceptionMsg()}")
+                        newCall("fmt", newLit"Socket tried to use an unknown protocol: {getCurrentExceptionMsg()}")
                       ),
                       wsDelStmt,
                       newCall("_wsMismatchProtocol", wsClientI)
@@ -1426,7 +1434,7 @@ socketToSsr.onmessage=function(m){
                     newStmtList(
                       newCall(
                         "echo",
-                        newCall("fmt", newStrLitNode"Unexpected socket error: {getCurrentExceptionMsg()}")
+                        newCall("fmt", newLit"Unexpected socket error: {getCurrentExceptionMsg()}")
                       ),
                       wsDelStmt,
                       newCall("__wsError", wsClientI)
@@ -1487,6 +1495,9 @@ socketToSsr.onmessage=function(m){
         of "notfound":
           detectReturnStmt(statement[1])
           notFoundNode = statement[1]
+        of "onexception":
+          detectReturnStmt(statement[1])
+          onException = statement[1]
         of "middleware":
           detectReturnStmt(statement[1])
           stmtList.insert(0, statement[1])
@@ -1512,7 +1523,7 @@ socketToSsr.onmessage=function(m){
   when enableDebug:
     stmtList.add(newCall(
       "info",
-      newCall("fmt", newStrLitNode"{reqMethod}::{urlPath}")
+      newCall("fmt", newLit"{reqMethod}::{urlPath}")
     ))
 
   # NodeJS Library
@@ -1594,6 +1605,17 @@ socketToSsr.onmessage=function(m){
     else:
       newEmptyNode(),
     setup,
+    newProc(
+      ident"__onException",
+      [
+        newEmptyNode(),
+        newIdentDefs(ident"url", ident"string"),
+        newIdentDefs(ident"body", ident"string"),
+        newIdentDefs(ident"e", newNimNode(nnkRefTy).add(newDotExpr(ident"system", ident"Exception"))),
+      ],
+      onException,
+      nnkTemplateDef
+    ),
     newProc(
       ident"__wsError",
       [newEmptyNode(), newIdentDefs(wsClientI, wsType)],
diff --git a/src/happyx/sugar/js.nim b/src/happyx/sugar/js.nim
index 53b81cddb..933251f83 100755
--- a/src/happyx/sugar/js.nim
+++ b/src/happyx/sugar/js.nim
@@ -380,7 +380,7 @@ macro buildJs*(body: untyped): untyped =
     if text.len != 0:
       result.add(newNimNode(nnkPragma).add(newColonExpr(
         ident"emit",
-        newStrLitNode(text)
+        newLit(text)
       )))
     if (i != splitted.len-1 or splitted.len == 1) and nimNodes.len != 0:
       result.add(nimNodes[i])
diff --git a/src/happyx/sugar/style.nim b/src/happyx/sugar/style.nim
index 2c61cc0c3..1bab78e8a 100644
--- a/src/happyx/sugar/style.nim
+++ b/src/happyx/sugar/style.nim
@@ -256,4 +256,4 @@ macro buildStyle*(body: untyped): untyped =
   css = css.replace(re2"\.(px|rem|em)\b", "$1")
   # properties
   css = css.replace(re2"\s*\-\s*([a-zA-Z][a-zA-Z0-9_]*)", "-$1")
-  newCall("fmt", newStrLitNode(css), newLit('<'), newLit('>'))
+  newCall("fmt", newLit(css), newLit('<'), newLit('>'))


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index f0dc6d282..212d1b64e 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -41,6 +41,7 @@ jobs:
           - ubuntu-latest
         nim_version:
           - '2.0.0'
+          - '2.0.4'
     env:
       TIMEOUT_EXIT_STATUS: 124
     steps:
@@ -94,8 +95,6 @@ jobs:
       matrix:
         os:
           - ubuntu-latest
-        nim_version:
-          - '2.0.0'
     env:
       TIMEOUT_EXIT_STATUS: 124
     steps:
@@ -122,8 +121,6 @@ jobs:
       matrix:
         os:
           - ubuntu-latest
-        nim_version:
-          - '2.0.0'
     env:
       TIMEOUT_EXIT_STATUS: 124
     steps:
@@ -150,8 +147,6 @@ jobs:
       matrix:
         os:
           - ubuntu-latest
-        nim_version:
-          - '2.0.0'
     env:
       TIMEOUT_EXIT_STATUS: 124
     steps:
@@ -178,8 +173,6 @@ jobs:
       matrix:
         os:
           - ubuntu-latest
-        nim_version:
-          - '2.0.0'
     env:
       TIMEOUT_EXIT_STATUS: 124
     steps:
diff --git a/tests/components/hello_world.nim b/tests/components/hello_world.nim
index 21cfb20f5..a3050b105 100644
--- a/tests/components/hello_world.nim
+++ b/tests/components/hello_world.nim
@@ -11,7 +11,7 @@ component HelloWorld:
       button:
         "Increase"
         @click:
-          echo self.counter
+          echo self.counter, " -> ", self.uniqCompId
           self.counter += 1.0
       button:
         "Go to /visit"
@@ -21,8 +21,8 @@ component HelloWorld:
   # @updated:
   #   echo self.render()
   
-  `script`:
-    echo self.counter
+  # `script`:
+  #   echo self.counter
   
   `style`:
     buildStyle:
diff --git a/tests/index.html b/tests/index.html
index 526e1cf2b..db397f77d 100644
--- a/tests/index.html
+++ b/tests/index.html
@@ -18,4 +18,4 @@
       padding: 0;
     }
   </style>
-</html>
\ No newline at end of file
+</html>
diff --git a/tests/testc1.nim b/tests/testc1.nim
index 40bafea27..a18f968ec 100644
--- a/tests/testc1.nim
+++ b/tests/testc1.nim
@@ -13,7 +13,7 @@ serve("127.0.0.1", 5000):
   post "/":
     "Hello world with POST method!"
 
-  "/calc/{left:int}{operator:string}{right:int}":
+  get "/calc/{left:int}{operator:string}{right:int}":
     if operator == "+":
       fmt"Result of {left} + {right} is {left + right}"
     elif operator == "-":
diff --git a/tests/testc20.nim b/tests/testc20.nim
index 98ea061de..de104ac34 100644
--- a/tests/testc20.nim
+++ b/tests/testc20.nim
@@ -41,3 +41,11 @@ serve "127.0.0.1", 5000:
 
   middleware:
     outHeaders["x"] = "y"
+  
+  onException:
+    # here built-in variables:
+    # `url`, `body` is string
+    # `e` is `ref Exception`
+    echo "URL: ", url
+    echo "BODY: ", body
+    echo "EXCEPTION: [", e.name, "] - ", e.msg
diff --git a/tests/testjs13.nim b/tests/testjs13.nim
index 170df4000..eae011412 100644
--- a/tests/testjs13.nim
+++ b/tests/testjs13.nim
@@ -19,11 +19,19 @@ component Test:
   someValue : string = "test"
   `template`:
     tDiv:
-      tInput(id = "test", "type" = "text", value=self.someValue):
-        @keyup(event):
-          self.someValue = $event.target.value
+      tInput(id = "test", type = "text", value=self.someValue):
+        @keyup:
+          echo 123
+          echo $ev.target.value
+          self.someValue = $ev.target.value
+        @input:
+          echo $ev.target.value
+        @click:
+          echo "input focused"
       tButton():
         "Click me"
+        @click:
+          echo 1
       tDiv():
         {self.someValue}