novac 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1574 -597
  3. package/bin/novac +468 -171
  4. package/bin/nvc +522 -0
  5. package/bin/nvml +78 -17
  6. package/demo.nv +0 -0
  7. package/demo_builtins.nv +0 -0
  8. package/demo_http.nv +0 -0
  9. package/examples/bf.nv +69 -0
  10. package/examples/math.nv +21 -0
  11. package/kits/birdAPI/kitdef.js +954 -0
  12. package/kits/kitRNG/kitdef.js +740 -0
  13. package/kits/kitSSH/kitdef.js +1272 -0
  14. package/kits/kitadb/kitdef.js +606 -0
  15. package/kits/kitai/kitdef.js +2185 -0
  16. package/kits/kitansi/kitdef.js +1402 -0
  17. package/kits/kitcanvas/kitdef.js +914 -0
  18. package/kits/kitclippy/kitdef.js +925 -0
  19. package/kits/kitformat/kitdef.js +1485 -0
  20. package/kits/kitgps/kitdef.js +1862 -0
  21. package/kits/kitlibproc/kitdef.js +3 -2
  22. package/kits/kitmatrix/ex.js +19 -0
  23. package/kits/kitmatrix/kitdef.js +960 -0
  24. package/kits/kitmorse/kitdef.js +229 -0
  25. package/kits/kitmpatch/kitdef.js +906 -0
  26. package/kits/kitnet/kitdef.js +1401 -0
  27. package/kits/kitnovacweb/README.md +1416 -143
  28. package/kits/kitnovacweb/kitdef.js +92 -2
  29. package/kits/kitnovacweb/nvml/executor.js +578 -176
  30. package/kits/kitnovacweb/nvml/index.js +2 -2
  31. package/kits/kitnovacweb/nvml/lexer.js +72 -69
  32. package/kits/kitnovacweb/nvml/parser.js +328 -159
  33. package/kits/kitnovacweb/nvml/renderer.js +770 -270
  34. package/kits/kitparse/kitdef.js +1688 -0
  35. package/kits/kitproto/kitdef.js +613 -0
  36. package/kits/kitqr/kitdef.js +637 -0
  37. package/kits/kitregex++/kitdef.js +1353 -0
  38. package/kits/kitrequire/kitdef.js +1599 -0
  39. package/kits/kitx11/kitdef.js +1 -0
  40. package/kits/kitx11/kitx11.js +2472 -0
  41. package/kits/kitx11/kitx11_conn.js +948 -0
  42. package/kits/kitx11/kitx11_worker.js +121 -0
  43. package/kits/libtea/kitdef.js +2691 -0
  44. package/kits/libterm/ex.js +285 -0
  45. package/kits/libterm/kitdef.js +1927 -0
  46. package/novac/LICENSE +21 -0
  47. package/novac/README.md +1823 -0
  48. package/novac/bin/novac +950 -0
  49. package/novac/bin/nvc +522 -0
  50. package/novac/bin/nvml +542 -0
  51. package/novac/demo.nv +245 -0
  52. package/novac/demo_builtins.nv +209 -0
  53. package/novac/demo_http.nv +62 -0
  54. package/novac/examples/bf.nv +69 -0
  55. package/novac/examples/math.nv +21 -0
  56. package/novac/kits/kitai/kitdef.js +2185 -0
  57. package/novac/kits/kitansi/kitdef.js +1402 -0
  58. package/novac/kits/kitformat/kitdef.js +1485 -0
  59. package/novac/kits/kitgps/kitdef.js +1862 -0
  60. package/novac/kits/kitlibfs/kitdef.js +231 -0
  61. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  62. package/novac/kits/kitmatrix/ex.js +19 -0
  63. package/novac/kits/kitmatrix/kitdef.js +960 -0
  64. package/novac/kits/kitmpatch/kitdef.js +906 -0
  65. package/novac/kits/kitnovacweb/README.md +1572 -0
  66. package/novac/kits/kitnovacweb/demo.nv +12 -0
  67. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  68. package/novac/kits/kitnovacweb/index.nova +12 -0
  69. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  70. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  71. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  72. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  73. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  74. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  75. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  76. package/novac/kits/kitparse/kitdef.js +1688 -0
  77. package/novac/kits/kitregex++/kitdef.js +1353 -0
  78. package/novac/kits/kitrequire/kitdef.js +1599 -0
  79. package/novac/kits/kitx11/kitdef.js +1 -0
  80. package/novac/kits/kitx11/kitx11.js +2472 -0
  81. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  82. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  83. package/novac/kits/libtea/tf.js +2691 -0
  84. package/novac/kits/libterm/ex.js +285 -0
  85. package/novac/kits/libterm/kitdef.js +1927 -0
  86. package/novac/node_modules/chalk/license +9 -0
  87. package/novac/node_modules/chalk/package.json +83 -0
  88. package/novac/node_modules/chalk/readme.md +297 -0
  89. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  90. package/novac/node_modules/chalk/source/index.js +225 -0
  91. package/novac/node_modules/chalk/source/utilities.js +33 -0
  92. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  93. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  94. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  95. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  96. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  97. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  98. package/novac/node_modules/commander/LICENSE +22 -0
  99. package/novac/node_modules/commander/Readme.md +1176 -0
  100. package/novac/node_modules/commander/esm.mjs +16 -0
  101. package/novac/node_modules/commander/index.js +24 -0
  102. package/novac/node_modules/commander/lib/argument.js +150 -0
  103. package/novac/node_modules/commander/lib/command.js +2777 -0
  104. package/novac/node_modules/commander/lib/error.js +39 -0
  105. package/novac/node_modules/commander/lib/help.js +747 -0
  106. package/novac/node_modules/commander/lib/option.js +380 -0
  107. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  108. package/novac/node_modules/commander/package-support.json +19 -0
  109. package/novac/node_modules/commander/package.json +82 -0
  110. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  111. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  112. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  113. package/novac/node_modules/node-addon-api/README.md +95 -0
  114. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  115. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  116. package/novac/node_modules/node-addon-api/index.js +14 -0
  117. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  118. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  119. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  120. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  121. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  122. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  123. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  124. package/novac/node_modules/node-addon-api/package.json +480 -0
  125. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  126. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  127. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  128. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  129. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  130. package/novac/node_modules/serialize-javascript/README.md +149 -0
  131. package/novac/node_modules/serialize-javascript/index.js +297 -0
  132. package/novac/node_modules/serialize-javascript/package.json +33 -0
  133. package/novac/package.json +27 -0
  134. package/novac/scripts/update-bin.js +24 -0
  135. package/novac/src/core/bstd.js +1035 -0
  136. package/novac/src/core/config.js +155 -0
  137. package/novac/src/core/describe.js +187 -0
  138. package/novac/src/core/emitter.js +499 -0
  139. package/novac/src/core/error.js +86 -0
  140. package/novac/src/core/executor.js +5606 -0
  141. package/novac/src/core/formatter.js +686 -0
  142. package/novac/src/core/lexer.js +1026 -0
  143. package/novac/src/core/nova_builtins.js +717 -0
  144. package/novac/src/core/nova_thread_worker.js +166 -0
  145. package/novac/src/core/parser.js +2181 -0
  146. package/novac/src/core/types.js +112 -0
  147. package/novac/src/index.js +28 -0
  148. package/novac/src/runtime/stdlib.js +244 -0
  149. package/package.json +6 -3
  150. package/scripts/update-bin.js +0 -0
  151. package/src/core/bstd.js +838 -362
  152. package/src/core/executor.js +2578 -170
  153. package/src/core/lexer.js +502 -54
  154. package/src/core/nova_builtins.js +21 -3
  155. package/src/core/parser.js +413 -72
  156. package/src/core/types.js +30 -2
  157. package/src/index.js +0 -0
  158. package/examples/example-project/README.md +0 -3
  159. package/examples/example-project/src/main.nova +0 -3
  160. package/src/core/environment.js +0 -0
  161. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -1,299 +1,1572 @@
1
- # kitnovacweb
1
+ # NVML — Nova Markup Language Reference v2
2
2
 
3
- Nova web kit NVML markup language with SSR, client scripts, and HTTP server.
3
+ NVML is the reactive templating language for novac web pages. It compiles to HTML with a built-in reactive signal system, virtual DOM diffing, component model, client-side router, slot system, CSS transitions, and Server-Sent Events. Files use the `.nvml` extension.
4
4
 
5
5
  ---
6
6
 
7
- ## Usage
7
+ ## Table of Contents
8
+
9
+ 1. [CLI — nvml](#cli--nvml)
10
+ 2. [File Structure](#file-structure)
11
+ 3. [Comments](#comments)
12
+ 4. [Lexer Tokens](#lexer-tokens)
13
+ 5. [@config](#config)
14
+ 6. [@visual](#visual)
15
+ 7. [@ss — Global Styles](#ss--global-styles)
16
+ 8. [@state — Reactive Signals](#state--reactive-signals)
17
+ 9. [@computed — Derived Signals](#computed--derived-signals)
18
+ 10. [@effect — Side Effects](#effect--side-effects)
19
+ 11. [@component — Component Definitions](#component--component-definitions)
20
+ 12. [@use — Component Imports](#use--component-imports)
21
+ 13. [@slot — Named Slot Content](#slot--named-slot-content)
22
+ 14. [@route — Client-Side Router](#route--client-side-router)
23
+ 15. [Elements](#elements)
24
+ 16. [Properties](#properties)
25
+ 17. [Inline Text](#inline-text)
26
+ 18. [One-Way Binding (->)](#one-way-binding--)
27
+ 19. [Two-Way Binding (<->)](#two-way-binding--)
28
+ 20. [Conditional Rendering (?)](#conditional-rendering-)
29
+ 21. [@each — Reactive Lists](#each--reactive-lists)
30
+ 22. [Transitions (~)](#transitions-)
31
+ 23. [Signal References (#)](#signal-references-)
32
+ 24. [Scoped Styles — [..]::ss](#scoped-styles---ss)
33
+ 25. [Scripts — {script}](#scripts---script)
34
+ 26. [Server-Side Nova Scripts](#server-side-nova-scripts)
35
+ 27. [Server-Side Node.js Scripts](#server-side-nodejs-scripts)
36
+ 28. [Triggered Server Scripts](#triggered-server-scripts)
37
+ 29. [Client-Side Scripts](#client-side-scripts)
38
+ 30. [@lang — Custom Language Extensions](#lang--custom-language-extensions)
39
+ 31. [bf — Brainfuck Runtime](#bf--brainfuck-runtime)
40
+ 32. [document API](#document-api)
41
+ 33. [Reactive Runtime API (client-side)](#reactive-runtime-api-client-side)
42
+ 34. [Virtual DOM Diffing](#virtual-dom-diffing)
43
+ 35. [Server-Sent Events (SSE)](#server-sent-events-sse)
44
+ 36. [Mutation Types — Full Reference](#mutation-types--full-reference)
45
+ 37. [Pipe Lists](#pipe-lists)
46
+ 38. [kitnovacweb Integration](#kitnovacweb-integration)
47
+ 39. [Full Example](#full-example)
8
48
 
9
- ```nova
10
- import "kitnovacweb"
49
+ ---
11
50
 
12
- document.setIndex("index.nvml")
13
- document.setPage("/about", "about.nvml")
14
- document.setNotFound("404.nvml")
15
- document.static("/public", "./public")
16
- document.serve(3000)
51
+ ## CLI — nvml
52
+
53
+ ```sh
54
+ nvml <port> <file.nvml> Serve on localhost:<port> at route /
55
+ nvml <port> <file.nvml> <route> Serve at a specific route path
56
+ nvml compile <file.nvml> Compile to HTML, print to stdout
57
+ nvml ast <file.nvml> Print parsed AST as JSON
58
+ nvml tokens <file.nvml> Print token stream as JSON
59
+ nvml check <file.nvml> Validate syntax (exit 0 = OK)
60
+ nvml --help Show help
61
+ nvml --version Show version
62
+ ```
63
+
64
+ The server **re-reads and re-compiles the file on every request** — no restart needed during development.
65
+
66
+ CORS headers are set automatically. Internal endpoints:
67
+
68
+ | Endpoint | Method | Description |
69
+ |----------|--------|-------------|
70
+ | `/_nvml/run` | `POST` | Execute triggered server-side Nova scripts, return mutations |
71
+ | `/_nvml/sse` | `GET` | Server-Sent Events stream for server-push signal updates |
72
+
73
+ ---
74
+
75
+ ## File Structure
76
+
77
+ An NVML file is a sequence of **top-level blocks**. Order does not matter except that `@visual` renders in the order it appears.
78
+
79
+ | Block | Purpose |
80
+ |-------|---------|
81
+ | `@config [ ... ]` | Document metadata → `<head>` |
82
+ | `@state [ ... ]` | Reactive signal declarations |
83
+ | `@computed [ ... ]` | Derived signal declarations |
84
+ | `@effect [ ... ]` | Side-effect declarations |
85
+ | `@component Name [ ... ]` | Reusable component definition |
86
+ | `@use Name, Name` | Declare component names used in this file |
87
+ | `@slot name [ ... ]` | Named slot content |
88
+ | `@route path [ ... ]` | Client-side route definition |
89
+ | `@lang Name [ ... ]` | Define a custom scripting language extension |
90
+ | `@visual [ ... ]` | Page body → `<body>` |
91
+ | `@ss [ ... ]` | Global CSS → `<style>` in `<head>` |
92
+
93
+ A minimal reactive page:
94
+
95
+ ```nvml
96
+ @config [ title='My App', lang='en' ]
97
+
98
+ @state [ count=0 ]
99
+
100
+ @ss [
101
+ {body} [ font-family='system-ui', background='#111', color='#eee' ]
102
+ ]
103
+
104
+ @visual [
105
+ {h1} [ text -> count ]
106
+ {button}='Increment' [ id='btn' ]
107
+
108
+ {script} [
109
+ language='nv', scope='server', trigger='click', target='btn',
110
+ [..]::code=(
111
+ document.setSignal("count", document.getSignal("count") + 1)
112
+ )
113
+ ]
114
+ ]
115
+ ```
116
+
117
+ ---
118
+
119
+ ## Comments
120
+
121
+ ```nvml
122
+ // single-line comment
123
+ /* multi-line block comment */
17
124
  ```
18
125
 
126
+ Both are stripped before parsing and produce no output.
127
+
19
128
  ---
20
129
 
21
- ## NVML Syntax
130
+ ## Lexer Tokens
131
+
132
+ | Token | Syntax | Example |
133
+ |-------|--------|---------|
134
+ | `AT_IDENT` | `@name` | `@config`, `@state`, `@visual` |
135
+ | `ELEMENT` | `{name}` | `{div}`, `{h1}`, `{element.button}` |
136
+ | `LBRACKET` | `[` | Opens a block |
137
+ | `RBRACKET` | `]` | Closes a block |
138
+ | `STRING` | `'text'` `"text"` `(multiline)` | Value |
139
+ | `COLONCOLON` | `::` | Parent-ref separator |
140
+ | `ARROW` | `->` | One-way binding |
141
+ | `DARROW` | `<->` | Two-way binding |
142
+ | `QUESTION` | `?` | Conditional render |
143
+ | `TILDE` | `~` | Transition hint |
144
+ | `HASH` | `#name` | Signal reference |
145
+ | `BANG_IDENT` | `!name` | Event emit shorthand |
146
+ | `IDENT` | bare word | Property key or value |
147
+ | `NUMBER` | `42`, `3.14` | Numeric value |
148
+ | `PIPE` | `\|` | Pipe list delimiter |
149
+ | `EQ` | `=` | Assignment |
150
+ | `COMMA` | `,` | Separator (always optional trailing) |
151
+ | `DOTDOT` | `[..]` | Parent element reference |
152
+ | `DOT` | `.` | Separator in namespaced element names |
153
+ | `EOF` | — | End of file |
154
+
155
+ ### Multiline Strings
156
+
157
+ Paren-delimited strings support multiple lines and nested parentheses:
22
158
 
23
- NVML (Nova Markup Language) is the templating language for novacweb pages.
159
+ ```nvml
160
+ [..]::code=(
161
+ let result = items.filter(x => x.active)
162
+ document.setSignal("filtered", result.length)
163
+ )
164
+ ```
165
+
166
+ Escape sequences inside quoted strings: `\n` `\t` `\r` `\\`
167
+
168
+ ---
24
169
 
25
- ### `@config` — document metadata
170
+ ## @config
171
+
172
+ Flat key-value block. All keys map to `<head>` elements or `<body>` attributes.
26
173
 
27
174
  ```nvml
28
175
  @config [
29
- title='My Site',
176
+ title='My App',
30
177
  lang='en',
31
178
  charset='UTF-8',
32
- description='Site description',
179
+ description='A reactive novac web app.',
180
+ author='Dev',
181
+ keywords=|nova, web, reactive|,
33
182
  viewport='width=device-width, initial-scale=1.0',
34
- favicon='/public/favicon.ico',
35
- theme-color='#1a1a2e',
36
- stylesheet='styles.css',
37
- scripts='app.js',
38
- og:title='My Site',
183
+ favicon='/favicon.ico',
184
+ theme-color='#6c63ff',
185
+ robots='index, follow',
186
+ canonical='https://example.com',
187
+ base='/app/',
188
+ stylesheet=|reset.css, main.css|,
189
+ scripts=|vendor.js, app.js|,
190
+ bodyClass='dark',
191
+ bodyId='root',
192
+ bodyStyle='overflow-x: hidden',
193
+ head='<link rel="preconnect" href="https://fonts.googleapis.com">',
194
+ og:title='My App',
195
+ og:description='Reactive novac.',
39
196
  og:image='/og.png',
197
+ og:url='https://example.com',
198
+ og:type='website',
199
+ twitter:card='summary_large_image',
200
+ twitter:title='My App',
201
+ twitter:description='Reactive.',
202
+ twitter:image='/twitter.png',
40
203
  ]
41
204
  ```
42
205
 
43
- All keys are freeform and map to appropriate `<head>` tags. Known keys:
44
- `title`, `lang`, `charset`, `description`, `author`, `keywords`, `viewport`,
45
- `favicon`, `base`, `robots`, `canonical`, `theme-color`, `stylesheet`, `scripts`,
46
- `bodyClass`, `bodyId`, `bodyStyle`, `head` (raw HTML injected into head),
47
- Open Graph (`og:title`, `og:description`, `og:image`, `og:url`, `og:type`),
48
- Twitter card (`twitter:card`, `twitter:title`, `twitter:description`, `twitter:image`).
206
+ ### Config Keys Reference
207
+
208
+ | Key | Output |
209
+ |-----|--------|
210
+ | `title` | `<title>` |
211
+ | `lang` | `<html lang="...">` |
212
+ | `charset` | `<meta charset="...">` (default: `UTF-8`) |
213
+ | `description` | `<meta name="description">` |
214
+ | `author` | `<meta name="author">` |
215
+ | `keywords` | `<meta name="keywords">` (pipe list = joined with `, `) |
216
+ | `viewport` | `<meta name="viewport">` |
217
+ | `favicon` | `<link rel="icon">` |
218
+ | `base` | `<base href="...">` |
219
+ | `theme-color` | `<meta name="theme-color">` |
220
+ | `robots` | `<meta name="robots">` |
221
+ | `canonical` | `<link rel="canonical">` |
222
+ | `stylesheet` | `<link rel="stylesheet">` (pipe list = one per value) |
223
+ | `scripts` | `<script src="...">` (pipe list = one per value) |
224
+ | `bodyClass` | `class="..."` on `<body>` |
225
+ | `bodyId` | `id="..."` on `<body>` |
226
+ | `bodyStyle` | `style="..."` on `<body>` |
227
+ | `head` | Raw HTML injected directly into `<head>` |
228
+ | `og:title` `og:description` `og:image` `og:url` `og:type` | `<meta property="og:...">` |
229
+ | `twitter:card` `twitter:title` `twitter:description` `twitter:image` | `<meta name="twitter:...">` |
49
230
 
50
231
  ---
51
232
 
52
- ### `@visual` — document body
233
+ ## @visual
53
234
 
54
- The main body of the page. Contains elements and script blocks.
235
+ Contains the page body. Direct children become contents of `<body>`.
55
236
 
56
237
  ```nvml
57
238
  @visual [
58
239
  {div} [
59
240
  class='container',
60
- {h1}='Hello World'
61
- {p} [ "Some text." ]
241
+ {h1}='Welcome'
242
+ {p}='Built with NVML v2'
62
243
  ]
63
244
  ]
64
245
  ```
65
246
 
66
247
  ---
67
248
 
68
- ### Elements
249
+ ## @ss — Global Styles
69
250
 
70
- **Any HTML tag name works:**
251
+ Generates a `<style>` block in `<head>`. Uses a CSS-flavored sub-syntax where element names are CSS selectors.
71
252
 
72
253
  ```nvml
73
- {div} [ ... ]
74
- {h1} [ ... ]
75
- {button} [ id='btn', class='primary', "Label" ]
76
- {img} [ src='/logo.png', alt='Logo' ]
77
- {input} [ type='text', placeholder='Enter text' ]
254
+ @ss [
255
+ {*} [ box-sizing='border-box', margin='0', padding='0' ]
256
+
257
+ {body} [
258
+ font-family='system-ui, sans-serif',
259
+ background='#111',
260
+ color='#eee',
261
+ display='flex',
262
+ align-items='center',
263
+ justify-content='center',
264
+ min-height='100vh',
265
+ ]
266
+
267
+ {.card} [
268
+ border-radius='10px',
269
+ padding='2.5rem',
270
+ background='#1e1e1e',
271
+ box-shadow='0 4px 24px #0008',
272
+ ]
273
+
274
+ {button:hover} [ background='#5548e0' ]
275
+ {.card h2} [ font-size='1.4rem', margin-bottom='0.5rem' ]
276
+ {#hero} [ min-height='60vh' ]
277
+ ]
78
278
  ```
79
279
 
80
- **Inline text shorthand:**
280
+ `{selector} [ property='value', ... ]` renders as:
281
+
282
+ ```css
283
+ selector {
284
+ property: value;
285
+ }
286
+ ```
287
+
288
+ Shorthand inside a selector block: `{property}='value'` → `property: value;`
289
+
290
+ CSS variables:
291
+ ```nvml
292
+ @ss [
293
+ {:root} [
294
+ --brand='#6c63ff',
295
+ --surface='#1e1e1e',
296
+ --nvml-dur-fade='0.3s',
297
+ ]
298
+ ]
299
+ ```
300
+
301
+ ---
302
+
303
+ ## @state — Reactive Signals
304
+
305
+ Declares reactive signals. All entries are automatically tracked. Every element with a binding to a signal updates automatically when the signal value changes.
306
+
307
+ ```nvml
308
+ @state [
309
+ count=0,
310
+ name='World',
311
+ isLoggedIn=false,
312
+ theme='dark',
313
+ items='[]',
314
+ selectedId=null,
315
+ ]
316
+ ```
317
+
318
+ The initial values are serialized to `window.__nvml.state` and the reactive runtime script is injected into `<head>`. Signals are accessible client-side:
319
+
320
+ ```js
321
+ window.__nvml.get('count') // read
322
+ window.__nvml.set('count', 42) // write — triggers all subscribers
323
+ window.__nvml.subscribe('count', val => { /* ... */ })
324
+ ```
325
+
326
+ ---
327
+
328
+ ## @computed — Derived Signals
329
+
330
+ Computed signals are derived from other signals. Nova code is evaluated server-side at render time to get an initial value. Client-side re-evaluation happens via `/_nvml/run` when dependencies change.
331
+
332
+ ```nvml
333
+ @computed [
334
+ doubled=( document.getSignal("count") * 2 ),
335
+ greeting=( f"Hello, {document.getSignal('name')}!" ),
336
+ itemCount=( document.getSignal("items").length ),
337
+ isEven=( document.getSignal("count") % 2 === 0 ),
338
+ ]
339
+ ```
340
+
341
+ The code string is a novac expression evaluated with `document` in scope. Results are stored under the computed name in the signal store and update reactively.
342
+
343
+ ---
344
+
345
+ ## @effect — Side Effects
346
+
347
+ Effects run Nova code on the server (via `/_nvml/run`) when declared signal dependencies change.
348
+
349
+ ```nvml
350
+ @effect [
351
+ // Single dependency → code string
352
+ count -> (
353
+ let c = document.getSignal("count")
354
+ if (c >= 10) {
355
+ document.toast("You reached 10!", 2000, "success")
356
+ }
357
+ ),
358
+
359
+ // Multiple dependencies → [dep1, dep2] list
360
+ [name, theme] -> (
361
+ document.console(f"name or theme changed", "log")
362
+ ),
363
+
364
+ // Wildcard: run on ANY signal change
365
+ * -> (
366
+ document.setSignal("lastUpdated", Date.now())
367
+ ),
368
+ ]
369
+ ```
370
+
371
+ Format: `deps -> (novac code)` where deps is:
372
+ - A single signal name: `count`
373
+ - A bracket list: `[count, name]`
374
+ - Wildcard: `*`
375
+
376
+ ---
377
+
378
+ ## @component — Component Definitions
379
+
380
+ Define reusable components. A component is a named block of NVML elements that can be instantiated anywhere as `{ComponentName}`.
381
+
382
+ ```nvml
383
+ @component Card [
384
+ {div} [
385
+ class='card',
386
+ [..]::ss [
387
+ {padding}='2rem',
388
+ {border-radius}='10px',
389
+ {background}='#1e1e1e',
390
+ {box-shadow}='0 4px 24px #0008',
391
+ ]
392
+ @slot header [] // named slot outlet
393
+ @slot [] // default slot outlet
394
+ @slot footer []
395
+ ]
396
+ ]
397
+
398
+ @component Button [
399
+ {button} [
400
+ class='btn',
401
+ [..]::ss [
402
+ {background}='#6c63ff',
403
+ {color}='white',
404
+ {border}='none',
405
+ {border-radius}='6px',
406
+ {padding}='0.6rem 1.5rem',
407
+ {cursor}='pointer',
408
+ {font-size}='1rem',
409
+ ]
410
+ @slot []
411
+ ]
412
+ ]
413
+
414
+ @component Badge [
415
+ {span} [
416
+ class='badge',
417
+ [..]::ss [
418
+ {background}='#6c63ff22',
419
+ {color}='#6c63ff',
420
+ {border-radius}='999px',
421
+ {padding}='0.2rem 0.7rem',
422
+ {font-size}='0.8rem',
423
+ ]
424
+ @slot []
425
+ ]
426
+ ]
427
+ ```
428
+
429
+ ### Using Components
430
+
431
+ Declare used components with `@use`, then use as `{ComponentName}`:
432
+
433
+ ```nvml
434
+ @use Card, Button, Badge
435
+
436
+ @visual [
437
+ {Card} [
438
+ @slot header [
439
+ {h2}='Card Title'
440
+ {Badge}='New'
441
+ ]
442
+ @slot [
443
+ {p}='Card body content goes here.'
444
+ {Button}='Save'
445
+ ]
446
+ @slot footer [
447
+ {small}='Last updated today'
448
+ ]
449
+ ]
450
+ ]
451
+ ```
452
+
453
+ Inline-defined components (in the same file) are instantiated directly. External components (declared via `@use` but defined elsewhere) render as `<div data-component="Name" data-props="...">` for client-side hydration.
454
+
455
+ ---
456
+
457
+ ## @use — Component Imports
458
+
459
+ Declare which component names are used in this page. Required for external components; optional but recommended for inline-defined ones.
460
+
461
+ ```nvml
462
+ @use Card, Button, Modal, Tooltip, Dropdown
463
+ ```
464
+
465
+ ---
466
+
467
+ ## @slot — Named Slot Content
468
+
469
+ Define named slot content at the top level to pass into components:
470
+
471
+ ```nvml
472
+ @slot header [
473
+ {h2}='Page Title'
474
+ {p}='Subtitle'
475
+ ]
476
+
477
+ @slot footer [
478
+ {small}='© 2025 novac'
479
+ ]
480
+ ```
481
+
482
+ Inside a `@component`, use `@slot name` as the insertion point:
483
+
484
+ ```nvml
485
+ @component Layout [
486
+ {div} [
487
+ class='layout',
488
+ {header} [ @slot header ]
489
+ {main} [ @slot ] // unnamed = default slot
490
+ {footer} [ @slot footer ]
491
+ ]
492
+ ]
493
+ ```
494
+
495
+ ---
496
+
497
+ ## @route — Client-Side Router
498
+
499
+ Define client-side routes. The reactive runtime includes a history API router that renders matching route bodies into `#nvml-router-outlet` (or `<body>` if absent). Route params inject into the signal store.
500
+
501
+ ```nvml
502
+ @visual [
503
+ {nav} [
504
+ {a} [ data-nvml-link='/' ]='Home'
505
+ {a} [ data-nvml-link='/about' ]='About'
506
+ {a} [ data-nvml-link='/user/42' ]='Profile'
507
+ ]
508
+ {div} [ id='nvml-router-outlet' ]
509
+ ]
510
+
511
+ @route '/' [
512
+ {h1}='Home Page'
513
+ {p}='Welcome!'
514
+ ]
515
+
516
+ @route '/about' [
517
+ {h1}='About'
518
+ {p}='Built with NVML v2.'
519
+ ]
520
+
521
+ @route '/user/:id' [
522
+ {h1}='User Profile'
523
+ {p} [ text -> id ]='Loading...' // id signal auto-set from :id param
524
+ ]
525
+ ```
526
+
527
+ Navigation: use `data-nvml-link="/path"` on any element, or call `window._nvmlNavigate('/path')` from JS. Route params (`:name`) are automatically written as signals when a route matches, so all bindings on them update reactively.
528
+
529
+ ---
530
+
531
+ ## Elements
532
+
533
+ Any HTML tag name is valid as `{tagname}`:
534
+
535
+ ```nvml
536
+ {div} {span} {p} {h1} {h2} {h3} {h4} {h5} {h6}
537
+ {a} {button} {input} {form} {label} {select} {option} {textarea}
538
+ {img} {video} {audio} {canvas} {iframe} {picture} {source}
539
+ {ul} {ol} {li} {dl} {dt} {dd}
540
+ {table} {thead} {tbody} {tfoot} {tr} {td} {th} {colgroup} {col}
541
+ {nav} {header} {footer} {main} {section} {article} {aside} {figure} {figcaption}
542
+ {details} {summary} {dialog} {template} {slot}
543
+ {pre} {code} {blockquote} {cite} {q} {abbr} {time} {mark}
544
+ {svg} {path} {circle} {rect} {line} {polyline} {polygon}
545
+ // ... any valid HTML tag
546
+ ```
547
+
548
+ ### Namespaced Elements
549
+
550
+ ```nvml
551
+ {element.button} // → <button>
552
+ {element.input} // → <input>
553
+ {ui.Card} // → component named Card (last segment used as tag/component name)
554
+ ```
555
+
556
+ ### Void Elements (self-closing)
557
+
558
+ `area base br col embed hr img input link meta param source track wbr`
559
+
560
+ ```nvml
561
+ {img} [ src='/logo.png', alt='Logo' ]
562
+ {input} [ type='text', placeholder='Search', id='searchInput' ]
563
+ {br}
564
+ {hr}
565
+ ```
566
+
567
+ ---
568
+
569
+ ## Properties
570
+
571
+ Set inside a `[ ... ]` block with `key=value` syntax:
572
+
573
+ ```nvml
574
+ {div} [
575
+ id='main',
576
+ class='container dark',
577
+ style='padding: 1rem',
578
+ tabindex=0,
579
+ role='main',
580
+ aria-label='Main content',
581
+ ]
582
+ ```
583
+
584
+ **Boolean properties** (no `=value`) render as bare attributes:
585
+
586
+ ```nvml
587
+ {input} [ type='checkbox', checked, disabled ]
588
+ {video} [ autoplay, loop, muted, controls ]
589
+ {details} [ open ]
590
+ ```
591
+
592
+ **Unknown property names** automatically render as `data-<name>`:
593
+
594
+ ```nvml
595
+ {div} [ my-thing='hello' ] // → <div data-my-thing="hello">
596
+ ```
597
+
598
+ **Value types:**
599
+
600
+ | Form | Example | Result |
601
+ |------|---------|--------|
602
+ | Single-quoted | `class='hero'` | String |
603
+ | Double-quoted | `id="myDiv"` | String |
604
+ | Paren multiline | `title=(long text)` | String |
605
+ | Number | `tabindex=0` | Number → string |
606
+ | Bare identifier | `type=text` | String |
607
+ | Pipe list | `keywords=\|a, b\|` | Array |
608
+ | Boolean flag | `checked` | bare attribute |
609
+
610
+ ---
611
+
612
+ ## Inline Text
613
+
614
+ Set element text directly with `{tag}='text'`:
81
615
 
82
616
  ```nvml
83
617
  {h1}='Page Title'
84
- {p}='Some text'
618
+ {p}='Some paragraph text.'
619
+ {button}='Click Me'
620
+ {a}='Go Home' [ href='/' ]
621
+ {span}='inline'
622
+
623
+ // Combined with property block:
624
+ {h1}='Welcome' [ id='heading', class='hero-title' ]
85
625
  ```
86
626
 
87
- **Namespaced elements** (`namespaces=|element|` in config to enable):
627
+ ---
628
+
629
+ ## One-Way Binding (->)
630
+
631
+ Bind an element property to a signal. The element updates automatically whenever the signal changes. The initial DOM value is set from the signal's current value.
88
632
 
89
633
  ```nvml
90
- {element.button} [ ... ] // <button>
91
- {element.div} [ ... ] // <div>
634
+ {p} [ text -> count ] // textContent mirrors count
635
+ {div} [ html -> richContent ] // innerHTML with virtual DOM diffing
636
+ {img} [ src -> avatarUrl ]
637
+ {a} [ href -> currentUrl ]
638
+ {div} [ class -> activeClass ]
639
+ {div} [ style -> dynamicStyle ]
640
+ {div} [ hidden -> isHidden ]
641
+ {input} [ placeholder -> hint ]
642
+ {button} [ disabled -> isLoading ]
643
+ {span} [ aria-label -> statusLabel ]
92
644
  ```
93
645
 
94
- **Nesting:**
646
+ **Bindable prop names:**
647
+
648
+ | Prop | Element update |
649
+ |------|----------------|
650
+ | `text` | `element.textContent` |
651
+ | `html` | Virtual DOM patch of `element.innerHTML` |
652
+ | `value` | `element.value` |
653
+ | `checked` | `element.checked` |
654
+ | `class` | `element.className` |
655
+ | `style` | `element.style.cssText` |
656
+ | `href` | `element.href` |
657
+ | `src` | `element.src` |
658
+ | `disabled` | `element.disabled` |
659
+ | `hidden` | `element.style.display` |
660
+ | `placeholder` | `element.placeholder` |
661
+ | Any other name | `element.setAttribute(prop, val)` |
662
+
663
+ ---
664
+
665
+ ## Two-Way Binding (<->)
666
+
667
+ Syncs an input element's value with a signal in both directions. When the signal changes → input updates. When the user types → signal updates.
668
+
669
+ ```nvml
670
+ {input} [ value <-> name, type='text', placeholder='Your name' ]
671
+ {input} [ checked <-> isLoggedIn, type='checkbox' ]
672
+ {textarea} [ value <-> bio, rows=4 ]
673
+ {select} [ value <-> selectedTheme ]
674
+ {input} [ value <-> searchQuery, type='search' ]
675
+ {input} [ value <-> volume, type='range', min=0, max=100 ]
676
+ ```
677
+
678
+ Listens on `input` events for `input`/`textarea`/`select`, `change` for everything else. Checkbox `<->` syncs `.checked` (boolean), all others sync `.value` (string).
679
+
680
+ ---
681
+
682
+ ## Conditional Rendering (?)
683
+
684
+ Conditionally render an element based on signal truthiness. The element is shown/hidden reactively without being destroyed or re-created.
95
685
 
96
686
  ```nvml
97
687
  {div} [
98
- class='card',
99
- {h2}='Card Title'
100
- {p}='Card body text.'
101
- {button} [ onclick='doSomething()', "Click" ]
688
+ ? isLoggedIn {div} [ class='dashboard' ]
689
+ ? isError {div} [ class='error-box', text -> errorMsg ]
690
+ ? isLoading {div} [ class='spinner' ]
102
691
  ]
103
692
  ```
104
693
 
694
+ Multiple conditions can exist in the same parent. Each conditional element gets `data-nvml-if="signalName"`. The runtime shows/hides reactively when the signal changes. Combine with transitions for animated show/hide:
695
+
696
+ ```nvml
697
+ ? isOpen {div} [ class='modal', opacity ~ fade ]
698
+ ? hasNotif {div} [ class='notification', opacity ~ slide ]
699
+ ```
700
+
105
701
  ---
106
702
 
107
- ### `[..]::ss`element-scoped styles
703
+ ## @eachReactive Lists
108
704
 
109
- Styles inside `[..]::ss` apply only to the parent element.
110
- The selector is automatically generated from the element's `id` (if set) or a `data-nvml-id` attribute.
705
+ Render a list from a signal array with keyed diffing. The DOM updates efficiently when the array changes — only affected items are touched.
111
706
 
112
707
  ```nvml
113
- {p} [
114
- id='intro',
115
- [..]::ss [
116
- {color}='#aaa',
117
- {font-size}='1.2rem',
118
- {margin-top}='1rem',
708
+ {ul} [
709
+ @each items as item [
710
+ {li} [ text -> item ]
119
711
  ]
120
- "Some text."
121
712
  ]
122
713
  ```
123
714
 
124
- Inside `[..]::ss`, the `{property}='value'` shorthand defines CSS declarations.
125
- Full CSS rules can also be written as `{selector} [ property='value' ]`.
715
+ With index variable:
716
+
717
+ ```nvml
718
+ {ol} [
719
+ @each todos as todo [
720
+ {li} [
721
+ {span} [ text -> todo ]
722
+ ]
723
+ ]
724
+ ]
725
+ ```
726
+
727
+ Nested with bindings:
728
+
729
+ ```nvml
730
+ {div} [ class='list' ]
731
+ @each users as user [
732
+ {div} [
733
+ class='user-card',
734
+ {h3} [ text -> user ]
735
+ {p}='Active'
736
+ ]
737
+ ]
738
+ ]
739
+ ```
740
+
741
+ `@each` renders a container `<div data-nvml-each="signal" data-nvml-template="...template...">`. The reactive runtime clones the template per item and uses keyed diffing to patch the list when the signal array changes. Update the signal via `window.__nvml.set('items', newArray)` or via a triggered server script with `document.setSignal("items", newArray)`.
126
742
 
127
743
  ---
128
744
 
129
- ### `@ss` — global styles
745
+ ## Transitions (~)
746
+
747
+ Attach CSS enter/leave animations to elements. The transition name generates `@keyframes` rules automatically.
748
+
749
+ ```nvml
750
+ {div} [ class='modal', opacity ~ fade ]
751
+ {li} [ class='item', opacity ~ slide ]
752
+ {div} [ class='toast', opacity ~ pop ]
753
+ ```
754
+
755
+ This generates into `<head>`:
756
+
757
+ ```css
758
+ .nvml-enter-fade { animation: nvml-enter-fade var(--nvml-dur-fade, 0.25s) ease both; }
759
+ .nvml-leave-fade { animation: nvml-leave-fade var(--nvml-dur-fade, 0.25s) ease both; }
760
+ @keyframes nvml-enter-fade { from { opacity:0; transform: translateY(8px); } to { opacity:1; transform: none; } }
761
+ @keyframes nvml-leave-fade { from { opacity:1; transform: none; } to { opacity:0; transform: translateY(8px); } }
762
+ ```
763
+
764
+ Control duration per transition via CSS variable in `@ss`:
130
765
 
131
766
  ```nvml
132
767
  @ss [
133
- {body} [
134
- background='#111',
135
- color='white',
136
- font-family='system-ui, sans-serif',
768
+ {:root} [
769
+ --nvml-dur-fade='0.3s',
770
+ --nvml-dur-slide='0.15s',
771
+ --nvml-dur-pop='0.2s',
137
772
  ]
138
- {.container} [
139
- max-width='900px',
140
- margin='0 auto',
773
+ ]
774
+ ```
775
+
776
+ The `hide`, `show`, and `remove` mutations also accept a transition name as their last argument:
777
+
778
+ ```nova
779
+ document.hide("modal", "fade")
780
+ document.show("drawer", "block", "slide")
781
+ document.remove("toast", "pop")
782
+ ```
783
+
784
+ ---
785
+
786
+ ## Signal References (#)
787
+
788
+ Reference a signal value inline using `#signalName`:
789
+
790
+ ```nvml
791
+ // In @computed and @effect code
792
+ @computed [
793
+ doubled=( #count * 2 ),
794
+ greeting=( f"Hello {#name}!" ),
795
+ ]
796
+
797
+ // As a property value shorthand — equivalent to one-way binding
798
+ {p} [ text=#count ] // same as: text -> count
799
+ {img} [ src=#avatarUrl ] // same as: src -> avatarUrl
800
+ {div} [ class=#theme ] // same as: class -> theme
801
+ ```
802
+
803
+ ---
804
+
805
+ ## Scoped Styles — [..]::ss
806
+
807
+ Attaches CSS scoped to the parent element only. Renders as a `<style>` block immediately before the element, scoped to the element's `id` (or a generated `data-nvml-id`).
808
+
809
+ ```nvml
810
+ {div} [
811
+ id='hero',
812
+ [..]::ss [
813
+ {background}='linear-gradient(135deg, #6c63ff, #3ecfcf)',
814
+ {min-height}='60vh',
815
+ {display}='flex',
816
+ {align-items}='center',
817
+ {justify-content}='center',
818
+ {padding}='4rem 2rem',
141
819
  ]
820
+ {h1}='Welcome'
142
821
  ]
143
822
  ```
144
823
 
824
+ Renders as:
825
+
826
+ ```html
827
+ <style>
828
+ #hero {
829
+ background: linear-gradient(135deg, #6c63ff, #3ecfcf);
830
+ min-height: 60vh;
831
+ ...
832
+ }
833
+ </style>
834
+ <div id="hero">
835
+ <h1>Welcome</h1>
836
+ </div>
837
+ ```
838
+
839
+ Full CSS rule blocks inside `[..]::ss`:
840
+
841
+ ```nvml
842
+ {div} [
843
+ id='card',
844
+ [..]::ss [
845
+ {#card} [ padding='2rem', border-radius='10px' ]
846
+ {#card:hover} [ box-shadow='0 8px 32px #0006' ]
847
+ {#card h2} [ font-size='1.4rem', font-weight='600' ]
848
+ {#card .badge} [ opacity='0.7' ]
849
+ ]
850
+ ]
851
+ ```
852
+
853
+ Inline string form:
854
+
855
+ ```nvml
856
+ {p} [ [..]::ss='color: red; font-weight: bold;' ]
857
+ ```
858
+
145
859
  ---
146
860
 
147
- ### `{script}` — scripts
861
+ ## Scripts — {script}
862
+
863
+ | Property | Values | Description |
864
+ |----------|--------|-------------|
865
+ | `language` / `lang` | `js`, `novac`, `nv`, `nodejs`, or any name registered with `@lang` | Script language (default: `js`) |
866
+ | `scope` | `server`, `client` | Where code runs (default: `client`). For `nodejs` and `@lang` langs, scope is derived from `runtime_language`. |
867
+ | `trigger` | `click`, `input`, `change`, `submit`, `keydown`, ... | Event(s) that trigger a server script. Comma-separated for multiple. |
868
+ | `target` | element `id` | Element to attach the trigger to |
869
+ | `src` | URL | External script source |
870
+ | `defer` | boolean | Add `defer` attribute |
871
+ | `async` | boolean | Add `async` attribute |
148
872
 
149
- **Client-side JS:**
873
+ Code is set via `[..]::code`:
150
874
 
151
875
  ```nvml
152
876
  {script} [
153
877
  language='js',
154
878
  scope='client',
155
879
  [..]::code=(
156
- document.querySelector('button').addEventListener('click', () => {
157
- alert('hello');
880
+ window.__nvml.subscribe('theme', val => {
881
+ document.documentElement.setAttribute('data-theme', val);
158
882
  });
159
883
  )
160
884
  ]
161
885
  ```
162
886
 
163
- **Server-side Nova (runs at request time, can mutate the document):**
887
+ ---
888
+
889
+ ## Server-Side Nova Scripts
890
+
891
+ `language='novac'` (or `'nv'`) + `scope='server'` with no `trigger` → runs **at render time**. Mutations are baked directly into the HTML. Rendered as an HTML comment.
164
892
 
165
893
  ```nvml
166
894
  {script} [
167
- language='novac',
895
+ language='nv',
168
896
  scope='server',
169
897
  [..]::code=(
170
- document.setConfig("title", "Dynamic Title")
171
- document.set("myElementId", "Dynamic content")
898
+ document.setTitle("Dynamic Page Title")
899
+ document.setConfig("description", "Built with novac")
900
+ document.set("greeting", f"Hello from the server!")
901
+ document.addStyle(":root { --server-color: #6c63ff; }")
902
+ document.setSignal("initialCount", 42)
903
+ document.addClass("hero", "loaded")
172
904
  )
173
905
  ]
174
906
  ```
175
907
 
176
- **Client-side Nova (compiled to JS and embedded):**
908
+ Output: `<!-- nv server script executed at render time -->`
909
+
910
+ ---
911
+
912
+ ## Triggered Server Scripts
913
+
914
+ `scope='server'` + `trigger` + `target` → generates client-side JavaScript that POSTs to `/_nvml/run` on the specified event(s) and applies returned mutations to the DOM.
177
915
 
178
916
  ```nvml
917
+ {button}='Submit' [ id='submitBtn' ]
918
+
179
919
  {script} [
180
- language='novac',
181
- scope='client',
920
+ language='nv', scope='server',
921
+ trigger='click', target='submitBtn',
922
+ [..]::code=(
923
+ let name = document.getValue("nameInput")
924
+ if (name equals "") {
925
+ document.toast("Name is required", 2000, "error")
926
+ give
927
+ }
928
+ document.setSignal("isLoading", true)
929
+ let result = fetch(https://api.example.com/submit({ name }))
930
+ document.setSignal("isLoading", false)
931
+ if (result.ok) {
932
+ document.toast(f"Welcome, {name}!", 3000, "success")
933
+ document.setSignal("isLoggedIn", true)
934
+ document.setSignal("userName", name)
935
+ } else {
936
+ document.toast("Submission failed", 2000, "error")
937
+ }
938
+ )
939
+ ]
940
+ ```
941
+
942
+ Multiple events (comma-separated):
943
+
944
+ ```nvml
945
+ {script} [
946
+ language='nv', scope='server',
947
+ trigger='click,keydown', target='searchInput',
182
948
  [..]::code=(
183
- // Nova code compiled via Emitter and embedded as JS
184
- print("hello from client")
949
+ let q = document.getValue("searchInput")
950
+ let results = fetch(https://api.example.com/search({ q }))
951
+ document.setSignal("results", results.body.items)
185
952
  )
186
953
  ]
187
954
  ```
188
955
 
189
- Supported `language` values: `js`, `javascript`, `novac`, `nv`
190
- Supported `scope` values: `server`, `client` (default: `client`)
956
+ The generated client-side script:
957
+ 1. Snapshots the live DOM state (textContent, value, className of all `id`'d elements + current signal state)
958
+ 2. POSTs `{ code, live: { elements, state, title, url, query } }` to `/_nvml/run`
959
+ 3. Receives `{ mutations }` and applies each via `window.__nvml.applyMutations`
191
960
 
192
- Script code can also be written as multiline strings using `( ... )` parentheses:
961
+ ---
962
+
963
+ ## Server-Side Node.js Scripts
964
+
965
+ `language='nodejs'` (or `'node'`) → runs **in Node.js** at render time (without a trigger) or on-demand via `/_nvml/run-node` (with a trigger). The full Node.js API, `require`, `process`, and `console` are available in scope.
966
+
967
+ Both `document` and `bf` are always available in the Node.js scope (same API as Nova server scripts).
193
968
 
194
969
  ```nvml
195
- [..]::code=(
196
- multi
197
- line
198
- code
199
- )
970
+ // At render time — runs once, mutations baked into HTML
971
+ {script} [
972
+ language='nodejs',
973
+ [..]::code=(
974
+ const os = require('os');
975
+ document.set('hostname', os.hostname());
976
+ document.set('platform', process.platform);
977
+ document.setTitle('Server: ' + os.hostname());
978
+ )
979
+ ]
980
+
981
+ // Triggered — runs per event, sends mutations to client
982
+ {button}='Get Server Info' [ id='infoBtn' ]
983
+
984
+ {script} [
985
+ language='nodejs',
986
+ trigger='click', target='infoBtn',
987
+ [..]::code=(
988
+ const os = require('os');
989
+ document.set('hostname', os.hostname());
990
+ document.toast('Info loaded', 1500, 'success');
991
+ )
992
+ ]
200
993
  ```
201
994
 
995
+ **Comparison — Nova vs nodejs:**
996
+
997
+ | Feature | `language='nv'` | `language='nodejs'` |
998
+ |---------|-----------------|---------------------|
999
+ | Runtime | Nova interpreter | Node.js VM (`vm.runInContext`) |
1000
+ | `require()` | ✗ | ✓ (full Node.js modules) |
1001
+ | Async/await | ✗ (Nova sync) | ✓ |
1002
+ | Nova syntax | ✓ | ✗ |
1003
+ | `document` API | ✓ | ✓ |
1004
+ | `bf` object | ✓ | ✓ |
1005
+ | Render-time | ✓ | ✓ |
1006
+ | Triggered | ✓ | ✓ (via `/_nvml/run-node`) |
1007
+
202
1008
  ---
203
1009
 
204
- ### `[..]` parent reference
1010
+ ## @lang Custom Language Extensions
205
1011
 
206
- `[..]` always refers to the nearest parent element. It's used for setting properties on the parent:
1012
+ Register a completely new language that can be used in `{script}` blocks. This is the plugin system for NVML you can implement any language on top of it.
207
1013
 
208
1014
  ```nvml
209
- {div} [
210
- [..]::ss [ {color}='red', ] // set ss on the div
211
- [..]::code=( console.log(1) ) // set code on the div
212
- [..]::id='myDiv' // set id on the div
1015
+ @lang {languageName} [
1016
+ runtime_language="nv" // "nv" | "js" | "nodejs"
1017
+ config="" // optional: JSON string, or path to a config.json file
1018
+ src="path/to/impl.js" // path to implementation file (server or client)
1019
+ code=( // or: inline implementation code
1020
+ ...implementation...
1021
+ )
1022
+ ]
1023
+ ```
1024
+
1025
+ ### `runtime_language` — Scope & Execution Model
1026
+
1027
+ | Value | Scope | How It Runs |
1028
+ |-------|-------|-------------|
1029
+ | `"nv"` / `"nova"` / `"novac"` | **Server** | Implementation + user code run in Nova interpreter. Same as a `scope='server'` nv script. |
1030
+ | `"nodejs"` / `"node"` | **Server** | Implementation + user code run in Node.js VM (`vm.runInContext`). Full `require`, `process`, etc. |
1031
+ | `"js"` | **Client** | Implementation is injected into the page as a `<script>`, then user code follows in the same IIFE. |
1032
+
1033
+ ### Object Availability in Every Lang Scope
1034
+
1035
+ All three runtime contexts always receive:
1036
+
1037
+ | Object | Description |
1038
+ |--------|-------------|
1039
+ | `document` | The full NVML document API (mutations collected and applied) |
1040
+ | `bf` | The Brainfuck runtime object (see [bf — Brainfuck Runtime](#bf--brainfuck-runtime)) |
1041
+ | `request` | Request context (triggered scripts): `{ method, path, query, state, elements }` |
1042
+
1043
+ ### How It Works
1044
+
1045
+ **Server-side (`nv` or `nodejs`):**
1046
+ 1. At executor time, NVML reads the `@lang` definition and stores a `NvmlLangDef` in `doc.langs[name]`.
1047
+ 2. When a `{script}[language='myLang']` element is encountered, the executor concatenates `langDef.code + '\n' + userCode` and runs the combined code in the appropriate runtime (Nova runner or Node.js VM).
1048
+ 3. At render time, the script element renders as a comment (render-time) or a fetch-based event handler (triggered).
1049
+
1050
+ **Client-side (`js`):**
1051
+ 1. The `@lang` implementation code is embedded in a `<script>` IIFE on the page.
1052
+ 2. Each `{script}[language='myLang']` block appends user code into the same IIFE, with `bf` available via `window.__nvml_bf`.
1053
+
1054
+ ### Examples
1055
+
1056
+ **A simple template language (client-side):**
1057
+ ```nvml
1058
+ @lang tmpl [
1059
+ runtime_language="js",
1060
+ code=(
1061
+ function tmplRender(template, data) {
1062
+ return template.replace(/\{\{(\w+)\}\}/g, (_, k) => data[k] ?? '');
1063
+ }
1064
+ )
1065
+ ]
1066
+
1067
+ {script} [
1068
+ language='tmpl',
1069
+ [..]::code=(
1070
+ const result = tmplRender('Hello, {{name}}!', { name: 'World' });
1071
+ document.getElementById('out').textContent = result;
1072
+ )
1073
+ ]
1074
+ ```
1075
+
1076
+ **A data-processing language (server-side Node.js):**
1077
+ ```nvml
1078
+ @lang dataql [
1079
+ runtime_language="nodejs",
1080
+ code=(
1081
+ const fs = require('fs');
1082
+ function query(file, fn) {
1083
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'));
1084
+ return Array.isArray(data) ? data.filter(fn) : data;
1085
+ }
1086
+ )
1087
+ ]
1088
+
1089
+ {script} [
1090
+ language='dataql',
1091
+ [..]::code=(
1092
+ const users = query('./data/users.json', u => u.active);
1093
+ document.setSignal('users', users);
1094
+ document.set('userCount', String(users.length));
1095
+ )
1096
+ ]
1097
+ ```
1098
+
1099
+ **A Nova-powered macro language (server-side Nova):**
1100
+ ```nvml
1101
+ @lang macros [
1102
+ runtime_language="nv",
1103
+ code=(
1104
+ func defineCard(title, content) {
1105
+ document.setHTML("cardTitle", f"<h2>{title}</h2>")
1106
+ document.setHTML("cardContent", f"<p>{content}</p>")
1107
+ }
1108
+ )
1109
+ ]
1110
+
1111
+ {script} [
1112
+ language='macros',
1113
+ [..]::code=(
1114
+ defineCard("Welcome", "This content came from a custom Nova macro language.")
1115
+ )
1116
+ ]
1117
+ ```
1118
+
1119
+ **Config file (JSON):**
1120
+ ```nvml
1121
+ @lang myLang [
1122
+ runtime_language="nodejs",
1123
+ config="langs/mylang.config.json",
1124
+ src="langs/mylang.js"
213
1125
  ]
214
1126
  ```
215
1127
 
1128
+ The `config` string (or the content of the JSON file) is available inside the implementation code via the `__langConfig` variable (parsed JSON object), injected before the implementation code runs.
1129
+
216
1130
  ---
217
1131
 
218
- ### Pipe lists
1132
+ ## bf — Brainfuck Runtime
1133
+
1134
+ **`bf` is available in every language scope** — Nova server scripts, Node.js server scripts, and client-side JavaScript. It's always injected automatically; no import or setup required.
1135
+
1136
+ On the client, `bf` is exposed as `window.__nvml_bf`. A fresh instance can be created with `window.__nvml_bf_make()`.
1137
+
1138
+ ### Object Properties & Methods
1139
+
1140
+ | Member | Type | Description |
1141
+ |--------|------|-------------|
1142
+ | `bf.tape` | `Uint8Array(30000)` | The cell tape. 30,000 cells, each a byte (0–255). |
1143
+ | `bf.pointer` | `number` (read-only) | Current data pointer index. |
1144
+ | `bf.output` | `string` (read-only) | Accumulated output from `.` instructions. |
1145
+ | `bf.input` | `string` (read/write) | Input buffer consumed by `,` instructions. |
1146
+ | `bf.cell(n?, v?)` | `number` | Get cell `n` (default: current). If `v` given, set cell `n` to `v & 0xFF`. |
1147
+ | `bf.run(code, input?)` | `string` | Execute BF source string. Returns output. |
1148
+ | `bf.reset()` | `bf` | Clear tape, pointer, and I/O buffers. Returns `bf` for chaining. |
1149
+
1150
+ ### Brainfuck Instruction Set
1151
+
1152
+ | Instruction | Action |
1153
+ |-------------|--------|
1154
+ | `>` | Move pointer right (wraps at 30000) |
1155
+ | `<` | Move pointer left (wraps at 0) |
1156
+ | `+` | Increment cell (wraps 255 → 0) |
1157
+ | `-` | Decrement cell (wraps 0 → 255) |
1158
+ | `.` | Output cell as ASCII char → appended to `bf.output` |
1159
+ | `,` | Read one char from `bf.input` into cell (EOF → 0) |
1160
+ | `[` | Jump past matching `]` if cell is 0 |
1161
+ | `]` | Jump back to matching `[` if cell is non-zero |
1162
+
1163
+ All other characters in the source are silently ignored (standard BF behaviour).
1164
+
1165
+ ### Safety Limits
1166
+
1167
+ - Maximum **10,000,000 operations** per `bf.run()` call before throwing `Error('[bf] max ops exceeded')`.
1168
+ - Tape wraps both at the high end (30000 → 0) and low end (0 → 29999).
219
1169
 
220
- For multi-value config keys:
1170
+ ### Examples
221
1171
 
222
1172
  ```nvml
223
- @config [
224
- namespaces=|element, custom|,
225
- keywords=|nova, web, kit|,
1173
+ // Server-side (nv) — compute something with BF and put it in the page
1174
+ {script} [
1175
+ language='nv', scope='server',
1176
+ [..]::code=(
1177
+ // Print "Hello World!" using Brainfuck
1178
+ let bfHello = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."
1179
+ let result = bf.run(bfHello)
1180
+ document.set("bfOutput", result)
1181
+ )
1182
+ ]
1183
+
1184
+ // Server-side (nodejs) — same thing
1185
+ {script} [
1186
+ language='nodejs',
1187
+ [..]::code=(
1188
+ const result = bf.run('++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.');
1189
+ document.set('bfOutput', result);
1190
+ )
1191
+ ]
1192
+
1193
+ // Client-side — run BF in the browser
1194
+ {script} [
1195
+ language='js', scope='client',
1196
+ [..]::code=(
1197
+ const result = window.__nvml_bf.run('++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.');
1198
+ document.getElementById('bfOutput').textContent = result;
1199
+
1200
+ // Fresh instance for another program
1201
+ const bf2 = window.__nvml_bf_make();
1202
+ bf2.input = 'A';
1203
+ bf2.run(',.'); // echo 'A'
1204
+ console.log(bf2.output); // 'A'
1205
+ )
226
1206
  ]
227
1207
  ```
228
1208
 
229
1209
  ---
230
1210
 
231
- ## `document` API
232
1211
 
233
- | Method | Description |
234
- |--------|-------------|
235
- | `document.setIndex(nvmlFile)` | Set NVML file for the `/` route |
236
- | `document.setPage(route, nvmlFile)` | Map a URL path to an NVML file |
237
- | `document.setNotFound(nvmlFile)` | Custom 404 page |
238
- | `document.serve(port)` | Start HTTP server (default: 3000) |
239
- | `document.stop()` | Stop the server |
240
- | `document.middleware(fn)` | Add middleware `fn(req, res, next)` |
241
- | `document.static(urlPath, dirPath)` | Serve static files from a directory |
242
- | `document.setHeader(key, value)` | Set a default HTTP response header |
243
1212
 
244
- Routes support `:param` segments: `document.setPage("/user/:id", "user.nvml")`
1213
+ ```nvml
1214
+ // Plain JavaScript — direct embed
1215
+ {script} [
1216
+ language='js', scope='client',
1217
+ [..]::code=(
1218
+ window.__nvml.subscribe('theme', val => {
1219
+ document.documentElement.className = val;
1220
+ });
1221
+ // restore theme from localStorage
1222
+ const saved = localStorage.getItem('theme');
1223
+ if (saved) window.__nvml.set('theme', saved);
1224
+ )
1225
+ ]
1226
+
1227
+ // External script
1228
+ {script} [ src='/js/chart.js', defer ]
1229
+ {script} [ src='https://cdn.jsdelivr.net/npm/alpinejs', async ]
1230
+ ```
245
1231
 
246
1232
  ---
247
1233
 
248
- ## Server-side Nova script API
1234
+ ## document API
1235
+
1236
+ Available inside `scope='server'` Nova scripts in both contexts (render time and triggered).
249
1237
 
250
- Inside `scope='server'` Nova scripts, a `document` proxy is available:
1238
+ ### At Render Time (baked into HTML)
251
1239
 
252
1240
  | Method | Description |
253
1241
  |--------|-------------|
254
- | `document.setConfig(key, val)` | Set a config value |
255
- | `document.getConfig(key)` | Get a config value |
256
- | `document.setTitle(title)` | Set page title |
257
- | `document.setMeta(key, val)` | Set a meta tag value |
258
- | `document.set(id, content)` | Set element text content by id |
259
- | `document.get(id)` | Get element text content by id |
260
- | `document.addStyle(css)` | Append to global stylesheet |
1242
+ | `document.setConfig(key, val)` | Set a config key |
1243
+ | `document.setTitle(title)` | Set `<title>` |
1244
+ | `document.setMeta(key, val)` | Set a meta value |
1245
+ | `document.set(id, text)` | Set element textContent by id |
1246
+ | `document.setHTML(id, html)` | Set element innerHTML by id |
1247
+ | `document.get(id)` | Get element text by id |
1248
+ | `document.setProp(id, key, val)` | Set element attribute by id |
1249
+ | `document.getProp(id, key)` | Get element attribute by id |
1250
+ | `document.addClass(id, cls)` | Append class to element |
1251
+ | `document.setClass(id, cls)` | Replace element class |
1252
+ | `document.addStyle(css)` | Append to global styles |
1253
+ | `document.addElementStyle(id, css)` | Append to element scoped CSS |
1254
+ | `document.hide(id)` | Set `display:none` on element |
1255
+ | `document.show(id, display?)` | Set `display:block` or custom |
1256
+ | `document.setSignal(name, val)` | Set signal initial value (baked into state JSON) |
1257
+ | `document.config` | Direct reference to `doc.config` object |
1258
+
1259
+ ### At Request Time (triggered via `/_nvml/run`)
1260
+
1261
+ All render-time methods plus reads from the live DOM snapshot:
1262
+
1263
+ | Method | Description |
1264
+ |--------|-------------|
1265
+ | `document.get(id)` | Live `textContent` of element |
1266
+ | `document.getValue(id)` | Live `.value` of input |
1267
+ | `document.getClass(id)` | Live `.className` |
1268
+ | `document.getConfig(key)` | Live config (title, url, etc.) |
1269
+ | `document.getSignal(name)` | Current signal value from client |
1270
+ | `document.setSignal(name, val)` | Push signal update to client |
1271
+ | `document.remove(id, transition?)` | Remove element from DOM |
1272
+ | `document.appendChild(id, html)` | Append HTML child to element |
1273
+ | `document.insertBefore(id, html)` | Insert HTML before element |
1274
+ | `document.insertAfter(id, html)` | Insert HTML after element |
1275
+ | `document.setStyle(id, key, val)` | Set inline style property |
1276
+ | `document.setCSSVar(name, val)` | Set CSS custom property on `:root` |
1277
+ | `document.setAttr(id, key, val)` | Set attribute |
1278
+ | `document.removeAttr(id, key)` | Remove attribute |
1279
+ | `document.removeClass(id, cls)` | Remove class |
1280
+ | `document.toggleClass(id, cls, force?)` | Toggle class |
1281
+ | `document.focus(id)` | Focus element |
1282
+ | `document.blur(id)` | Blur element |
1283
+ | `document.scroll(id, behavior?)` | Scroll element into view |
1284
+ | `document.patchList(id, items, template, key?)` | Keyed list diff and patch |
1285
+ | `document.navigate(path)` | Client-side router navigate |
1286
+ | `document.reload()` | Reload page |
1287
+ | `document.redirect(url)` | Full redirect |
1288
+ | `document.toast(msg, duration?, type?)` | Show NVML toast (`info` `success` `error`) |
1289
+ | `document.console(msg, level?)` | `console.log/warn/error` in browser |
1290
+ | `document.alert(msg)` | Browser `alert()` |
1291
+ | `document.push(signalName, value)` | SSE-push signal update to all clients |
1292
+ | `document.pushMutations(mutations)` | SSE-push mutation array to all clients |
261
1293
 
262
1294
  ---
263
1295
 
264
- ## How it works
1296
+ ## Reactive Runtime API (client-side)
1297
+
1298
+ Injected into `<head>` automatically when `@state`, `@computed`, or `@effect` is present. Available as `window.__nvml`:
1299
+
1300
+ ```js
1301
+ // Signals
1302
+ window.__nvml.get('count') // read signal value
1303
+ window.__nvml.set('count', 42) // write signal (triggers all subscribers + effects)
1304
+ window.__nvml.subscribe('count', val => { }) // subscribe; returns unsub function
1305
+ window.__nvml.notify('count') // manually trigger subscribers
1306
+ window.__nvml.state // raw signal object (direct access)
265
1307
 
266
- Each request:
267
- 1. The matching NVML file is read from disk (re-read every request always fresh)
268
- 2. NVML is tokenized parsed AST
269
- 3. AST is executed: `@config` populates metadata, `@visual` builds an element tree
270
- 4. `scope='server'` script blocks run immediately, mutating the document
271
- 5. The document is rendered to HTML: `@config` → `<head>`, element tree → `<body>`
272
- 6. `scope='client'` scripts are embedded as `<script>` tags
273
- 7. HTML is sent as the response
1308
+ // Mutations
1309
+ window.__nvml.applyMutations([ // apply a mutation array
1310
+ { type: 'setText', id: 'myEl', value: 'Hello' },
1311
+ { type: 'setSignal', name: 'count', value: 5 },
1312
+ ])
1313
+
1314
+ // Triggered server script
1315
+ window.__nvmlRun(`nova code string`) // POST to /_nvml/run, apply mutations
1316
+
1317
+ // Router
1318
+ window._nvmlNavigate('/path') // client-side navigate
1319
+ ```
1320
+
1321
+ ---
1322
+
1323
+ ## Virtual DOM Diffing
1324
+
1325
+ The `setHTML` mutation and `html ->` binding use structural diffing instead of `innerHTML =`. The algorithm:
1326
+
1327
+ 1. Walk old and new child nodes in parallel
1328
+ 2. Append missing nodes, remove stale nodes
1329
+ 3. For matching element nodes: diff attributes (`_diffAttrs`), recurse into children (`_diffChildren`)
1330
+ 4. For text nodes: update `textContent` only if changed
1331
+ 5. Replace mismatched node types or tag names entirely
1332
+
1333
+ This means you can call `document.setHTML("section", newHTML)` or bind `html -> richContent` in a triggered script without losing input focus, scroll position, or event listeners on unchanged subtrees.
1334
+
1335
+ The `patchList` mutation uses **keyed diffing**: existing list items are matched by a key field (or index), updated in-place, and stale items are removed — matching the behaviour of React/Vue key-based list reconciliation.
274
1336
 
275
1337
  ---
276
1338
 
277
- ## Installation
1339
+ ## Server-Sent Events (SSE)
1340
+
1341
+ NVML opens a persistent `GET /_nvml/sse` connection automatically when the page loads. The runtime reconnects after 3 seconds if the connection drops.
278
1342
 
279
- Copy the `kitnovacweb/` directory into your `nova_modules/` folder:
1343
+ From any triggered Nova script, push updates to **all connected clients** simultaneously:
280
1344
 
1345
+ ```nova
1346
+ // Push a signal update to all clients
1347
+ document.push("liveScore", 42)
1348
+ document.push("onlineCount", 128)
1349
+
1350
+ // Push arbitrary mutations to all clients
1351
+ document.pushMutations([
1352
+ { type: "setText", id: "ticker", value: "BTC $65,000" },
1353
+ { type: "toast", value: "Price updated!", duration: 2000, type: "info" },
1354
+ { type: "setSignal", name: "lastPrice", value: 65000 },
1355
+ ])
281
1356
  ```
282
- nova_modules/
283
- kitnovacweb/
284
- kitdef.js
285
- index.nova
286
- nova.kit.json
287
- nvml/
288
- index.js
289
- lexer.js
290
- parser.js
291
- executor.js
292
- renderer.js
1357
+
1358
+ SSE event types emitted by the server:
1359
+
1360
+ | Event name | Data format | Description |
1361
+ |------------|-------------|-------------|
1362
+ | `signal` | `{ "name": "...", "value": any }` | Update one signal on all clients |
1363
+ | `mutations` | `[{ type, ... }, ...]` | Apply mutation array on all clients |
1364
+
1365
+ This enables real-time features (live scores, dashboards, chat, notifications) without WebSockets.
1366
+
1367
+ ---
1368
+
1369
+ ## Mutation Types — Full Reference
1370
+
1371
+ All returned from `/_nvml/run` as `{ mutations: [...] }` and applied by `window.__nvml.applyMutations`. Also usable directly from client JS.
1372
+
1373
+ | `type` | Fields | Description |
1374
+ |--------|--------|-------------|
1375
+ | `setText` | `id, value` | `element.textContent = value` |
1376
+ | `setHTML` | `id, value` | Virtual-DOM patch of `innerHTML` |
1377
+ | `setProp` | `id, key, value` | `element.setAttribute(key, value)` |
1378
+ | `setAttr` | `id, key, value` | `element.setAttribute(key, value)` |
1379
+ | `removeAttr` | `id, key` | `element.removeAttribute(key)` |
1380
+ | `addClass` | `id, value` | `element.classList.add(value)` |
1381
+ | `removeClass` | `id, value` | `element.classList.remove(value)` |
1382
+ | `toggleClass` | `id, value, force?` | `element.classList.toggle(value, force)` |
1383
+ | `setClass` | `id, value` | `element.className = value` |
1384
+ | `addStyle` | `value` | Append `<style>` to `<head>` |
1385
+ | `addStyle` | `id, value` | Append scoped CSS for element |
1386
+ | `setStyle` | `id, key, value` | `element.style[key] = value` |
1387
+ | `setCSSVar` | `name, value` | `document.documentElement.style.setProperty(name, value)` |
1388
+ | `hide` | `id, transition?` | `display:none` with optional CSS transition |
1389
+ | `show` | `id, value?, transition?` | `display:block` or custom, with optional transition |
1390
+ | `remove` | `id, transition?` | Remove element from DOM with optional transition |
1391
+ | `appendChild` | `id, html` | Parse and append HTML as child |
1392
+ | `insertBefore` | `id, html` | Insert parsed HTML before element |
1393
+ | `insertAfter` | `id, html` | Insert parsed HTML after element |
1394
+ | `focus` | `id` | `element.focus()` |
1395
+ | `blur` | `id` | `element.blur()` |
1396
+ | `scroll` | `id, behavior?` | `element.scrollIntoView({ behavior })` |
1397
+ | `setSignal` | `name, value` | `window.__nvml.set(name, value)` |
1398
+ | `patchList` | `id, items, template, key?` | Keyed list diff and update |
1399
+ | `navigate` | `path` | Client-side router navigate |
1400
+ | `reload` | — | `location.reload()` |
1401
+ | `redirect` | `url` | `location.href = url` |
1402
+ | `alert` | `value` | `alert(value)` |
1403
+ | `toast` | `value, duration?, type?` | NVML toast (`info` `success` `error`) |
1404
+ | `console` | `value, level?` | `console[level](value)` |
1405
+ | `setConfig` | `key, value` | `key === 'title'` → `document.title = value` |
1406
+
1407
+ ---
1408
+
1409
+ ## Pipe Lists
1410
+
1411
+ Multi-value syntax surrounded by `|`:
1412
+
1413
+ ```nvml
1414
+ @config [
1415
+ stylesheet=|reset.css, main.css, theme.css|,
1416
+ scripts=|vendor.js, app.js|,
1417
+ keywords=|nova, reactive, web|,
1418
+ ]
293
1419
  ```
294
1420
 
295
- Then in your Nova file:
1421
+ Generates one `<link>`/`<script>` per value for `stylesheet`/`scripts`. Joins with `, ` for `keywords`.
1422
+
1423
+ ---
1424
+
1425
+ ## kitnovacweb Integration
1426
+
1427
+ Serve NVML files from a novac program:
296
1428
 
297
1429
  ```nova
298
1430
  import "kitnovacweb"
1431
+
1432
+ document.setIndex("index.nvml")
1433
+ document.setPage("/about", "about.nvml")
1434
+ document.setPage("/user/:id", "user.nvml")
1435
+ document.setNotFound("404.nvml")
1436
+ document.static("/public", "./public")
1437
+ document.middleware(func(req, res, next) => {
1438
+ core.print(f"{req.method} {req.url}")
1439
+ next()
1440
+ })
1441
+ document.setHeader("X-Powered-By", "novac")
1442
+ document.serve(3000)
1443
+ ```
1444
+
1445
+ | Method | Description |
1446
+ |--------|-------------|
1447
+ | `document.setIndex(file)` | NVML file for `/` |
1448
+ | `document.setPage(route, file)` | Map route to NVML file |
1449
+ | `document.setNotFound(file)` | 404 page |
1450
+ | `document.serve(port)` | Start HTTP server |
1451
+ | `document.stop()` | Stop server |
1452
+ | `document.middleware(fn)` | Add `fn(req, res, next)` middleware |
1453
+ | `document.static(urlPath, dirPath)` | Serve static files from directory |
1454
+ | `document.setHeader(key, val)` | Set a default response header |
1455
+
1456
+ ---
1457
+
1458
+ ## Full Example
1459
+
1460
+ ```nvml
1461
+ @config [
1462
+ title='Counter App',
1463
+ lang='en',
1464
+ charset='UTF-8',
1465
+ theme-color='#6c63ff',
1466
+ ]
1467
+
1468
+ @state [
1469
+ count=0,
1470
+ message='Click + or − to change the count.',
1471
+ isNegative=false,
1472
+ ]
1473
+
1474
+ @computed [
1475
+ doubled=( document.getSignal("count") * 2 ),
1476
+ absCount=( Math.abs(document.getSignal("count")) ),
1477
+ ]
1478
+
1479
+ @effect [
1480
+ count -> (
1481
+ let c = document.getSignal("count")
1482
+ document.setSignal("isNegative", c < 0)
1483
+ if (c >= 10) {
1484
+ document.toast(f"Reached {c}!", 2000, "success")
1485
+ }
1486
+ ),
1487
+ ]
1488
+
1489
+ @ss [
1490
+ {:root} [ --brand='#6c63ff', --surface='#1e1e1e', --nvml-dur-fade='0.25s' ]
1491
+
1492
+ {*} [ box-sizing='border-box', margin='0', padding='0' ]
1493
+ {body} [ font-family='system-ui, sans-serif', background='#111', color='#eee', display='flex', align-items='center', justify-content='center', min-height='100vh' ]
1494
+
1495
+ {.card} [ background='var(--surface)', border-radius='12px', padding='2.5rem 3rem', text-align='center', box-shadow='0 4px 32px #0008', min-width='320px' ]
1496
+ {.count} [ font-size='4rem', font-weight='700', margin='1rem 0', transition='color 0.2s' ]
1497
+ {.negative} [ color='#fc8181' ]
1498
+ {.controls} [ display='flex', gap='1rem', justify-content='center', margin-top='1.5rem' ]
1499
+ {button} [ background='var(--brand)', color='white', border='none', border-radius='8px', padding='0.7rem 2rem', font-size='1.1rem', cursor='pointer', transition='background 0.15s' ]
1500
+ {button:hover} [ background='#5548e0' ]
1501
+ {.sub} [ font-size='0.85rem', color='#888', margin-top='0.75rem' ]
1502
+ ]
1503
+
1504
+ @visual [
1505
+
1506
+ {div} [
1507
+ class='card',
1508
+
1509
+ {h1}='Counter'
1510
+
1511
+ {div} [
1512
+ id='countDisplay',
1513
+ class='count',
1514
+ text -> count,
1515
+ ]
1516
+
1517
+ {p} [ id='msg', text -> message ]
1518
+
1519
+ {div} [
1520
+ class='controls',
1521
+ {button}='−' [ id='decBtn' ]
1522
+ {button}='+' [ id='incBtn' ]
1523
+ {button}='Reset' [ id='resetBtn' ]
1524
+ ]
1525
+
1526
+ {p} [
1527
+ class='sub',
1528
+ 'Doubled: ',
1529
+ {span} [ id='doubledDisplay', text -> doubled ]
1530
+ ]
1531
+ ]
1532
+
1533
+ // Server scripts
1534
+ {script} [
1535
+ language='nv', scope='server', trigger='click', target='incBtn',
1536
+ [..]::code=(
1537
+ let c = document.getSignal("count")
1538
+ document.setSignal("count", c + 1)
1539
+ document.set("msg", f"Incremented to {c + 1}")
1540
+ )
1541
+ ]
1542
+
1543
+ {script} [
1544
+ language='nv', scope='server', trigger='click', target='decBtn',
1545
+ [..]::code=(
1546
+ let c = document.getSignal("count")
1547
+ document.setSignal("count", c - 1)
1548
+ document.set("msg", f"Decremented to {c - 1}")
1549
+ )
1550
+ ]
1551
+
1552
+ {script} [
1553
+ language='nv', scope='server', trigger='click', target='resetBtn',
1554
+ [..]::code=(
1555
+ document.setSignal("count", 0)
1556
+ document.set("msg", "Reset to zero.")
1557
+ document.toast("Counter reset", 1500, "info")
1558
+ )
1559
+ ]
1560
+
1561
+ // Client-side: add/remove .negative class based on isNegative signal
1562
+ {script} [
1563
+ language='js', scope='client',
1564
+ [..]::code=(
1565
+ window.__nvml.subscribe('isNegative', neg => {
1566
+ document.getElementById('countDisplay').classList.toggle('negative', neg);
1567
+ });
1568
+ )
1569
+ ]
1570
+
1571
+ ]
299
1572
  ```