@fun-land/fun-web 0.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 (71) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +515 -0
  3. package/coverage/clover.xml +136 -0
  4. package/coverage/coverage-final.json +4 -0
  5. package/coverage/lcov-report/base.css +224 -0
  6. package/coverage/lcov-report/block-navigation.js +87 -0
  7. package/coverage/lcov-report/dom.ts.html +961 -0
  8. package/coverage/lcov-report/favicon.png +0 -0
  9. package/coverage/lcov-report/index.html +146 -0
  10. package/coverage/lcov-report/mount.ts.html +202 -0
  11. package/coverage/lcov-report/prettify.css +1 -0
  12. package/coverage/lcov-report/prettify.js +2 -0
  13. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  14. package/coverage/lcov-report/sorter.js +196 -0
  15. package/coverage/lcov-report/state.ts.html +91 -0
  16. package/coverage/lcov.info +260 -0
  17. package/dist/esm/src/dom.d.ts +85 -0
  18. package/dist/esm/src/dom.js +207 -0
  19. package/dist/esm/src/dom.js.map +1 -0
  20. package/dist/esm/src/index.d.ts +7 -0
  21. package/dist/esm/src/index.js +5 -0
  22. package/dist/esm/src/index.js.map +1 -0
  23. package/dist/esm/src/mount.d.ts +21 -0
  24. package/dist/esm/src/mount.js +27 -0
  25. package/dist/esm/src/mount.js.map +1 -0
  26. package/dist/esm/src/state.d.ts +2 -0
  27. package/dist/esm/src/state.js +3 -0
  28. package/dist/esm/src/state.js.map +1 -0
  29. package/dist/esm/src/types.d.ts +3 -0
  30. package/dist/esm/src/types.js +3 -0
  31. package/dist/esm/src/types.js.map +1 -0
  32. package/dist/esm/tsconfig.publish.tsbuildinfo +1 -0
  33. package/dist/src/dom.d.ts +85 -0
  34. package/dist/src/dom.js +224 -0
  35. package/dist/src/dom.js.map +1 -0
  36. package/dist/src/index.d.ts +7 -0
  37. package/dist/src/index.js +24 -0
  38. package/dist/src/index.js.map +1 -0
  39. package/dist/src/mount.d.ts +21 -0
  40. package/dist/src/mount.js +31 -0
  41. package/dist/src/mount.js.map +1 -0
  42. package/dist/src/state.d.ts +2 -0
  43. package/dist/src/state.js +7 -0
  44. package/dist/src/state.js.map +1 -0
  45. package/dist/src/types.d.ts +3 -0
  46. package/dist/src/types.js +4 -0
  47. package/dist/src/types.js.map +1 -0
  48. package/dist/tsconfig.publish.tsbuildinfo +1 -0
  49. package/eslint.config.js +54 -0
  50. package/examples/README.md +67 -0
  51. package/examples/counter/bundle.js +219 -0
  52. package/examples/counter/counter.ts +112 -0
  53. package/examples/counter/index.html +44 -0
  54. package/examples/todo-app/Todo.ts +79 -0
  55. package/examples/todo-app/index.html +142 -0
  56. package/examples/todo-app/todo-app.ts +120 -0
  57. package/examples/todo-app/todo-bundle.js +410 -0
  58. package/jest.config.js +5 -0
  59. package/package.json +49 -0
  60. package/src/dom.test.ts +768 -0
  61. package/src/dom.ts +296 -0
  62. package/src/index.ts +25 -0
  63. package/src/mount.test.ts +220 -0
  64. package/src/mount.ts +39 -0
  65. package/src/state.test.ts +225 -0
  66. package/src/state.ts +2 -0
  67. package/src/types.ts +9 -0
  68. package/tsconfig.json +16 -0
  69. package/tsconfig.publish.json +6 -0
  70. package/wip/hx-magic-properties-plan.md +575 -0
  71. package/wip/next.md +22 -0
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,515 @@
1
+ # @fun-land/fun-web
2
+
3
+ A lightweight web component library for building reactive UIs with native DOM and compositional state management.
4
+
5
+ ## Why fun-web?
6
+
7
+ Build web UIs without a framework using:
8
+ - **Run-once components** that never re-render (sidesteps stale closures, memoization, render cycles)
9
+ - **Reactive state** following the FunState compositional pattern
10
+ - **Native DOM** elements (no virtual DOM)
11
+ - **TypeScript-first** APIs with full type inference
12
+ - **AbortSignal** for automatic cleanup
13
+
14
+ Perfect for embedding interactive components in static sites, building lightweight tools, or avoiding framework lock-in.
15
+
16
+ ## Features
17
+
18
+ - **Subscription-based reactivity** - State changes automatically update DOM
19
+ - **Keyed list rendering** - Efficient reconciliation without virtual DOM
20
+ - **Type-safe utilities** - Element types and events inferred automatically
21
+ - **Memory-safe by default** - AbortSignal prevents leaks
22
+ - **Framework-agnostic** - Just functions and elements
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ yarn add @fun-land/fun-web @fun-land/accessor
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```typescript
33
+ import {
34
+ h,
35
+ funState,
36
+ mount,
37
+ bindProperty,
38
+ on,
39
+ type Component,
40
+ type FunState,
41
+ } from "@fun-land/fun-web";
42
+
43
+
44
+ const Counter: Component<{state: FunState<number>}> = (signal, {state}) => {
45
+ // Component runs once - no re-rendering on state changes
46
+ const button = h("button", {}, `Count: ${state.get()}`);
47
+
48
+ // bindProperty subscribes to the past state and updates named property
49
+ bindProperty(button, "textContent", state, signal);
50
+
51
+ // Event handlers never go stale (component doesn't re-run)
52
+ on(button, "click", () => state.mod((n) => n + 1), signal);
53
+
54
+ return button;
55
+ };
56
+
57
+ // Create reactive state and mount
58
+ const mounted = mount(Counter, { state: funState(0) }, document.body);
59
+ ```
60
+
61
+ ## Core Concepts
62
+
63
+ ### Components Run Once
64
+
65
+ **The most important difference from React/Vue/Svelte:** fun-web components execute once when mounted, set up subscriptions, and never re-run.
66
+
67
+ ```typescript
68
+ const Counter: Component<{ count: FunState<number> }> = (signal, props) => {
69
+ console.log("Component runs once");
70
+
71
+ const display = h("div");
72
+ const button = h("button", {}, "Increment");
73
+
74
+ // This subscription handles updates, not re-rendering
75
+ bindProperty(display, "textContent", props.count, signal);
76
+ on(button, "click", () => props.count.mod(n => n + 1), signal);
77
+
78
+ return h("div", {}, [display, button]);
79
+ // Component function exits, but subscriptions keep working
80
+ };
81
+ ```
82
+
83
+ When `count` changes, the component **doesn't re-run**. Instead, the `bindProperty` subscription updates `display.textContent` directly.
84
+
85
+ **Problems this sidesteps:**
86
+
87
+ | Framework components | fun-web components |
88
+ |---------------------|-------------------|
89
+ | Re-execute on every state change | Execute once, subscriptions handle updates |
90
+ | Need memoization (`useMemo`, `useCallback`) | No re-execution = no stale closures to worry about |
91
+ | Virtual DOM diffing overhead | Direct DOM updates via subscriptions |
92
+ | Rules about when you can update state | Update state whenever you want |
93
+ | "Render cycles" and batching complexity | No render cycles, updates are just function calls |
94
+
95
+ **What this means:**
96
+ - Component functions are just **constructors** - they build UI once and set up reactive bindings
97
+ - State changes trigger **targeted DOM updates**, not full re-renders
98
+ - No "rendering" concept - just imperative DOM manipulation driven by reactive state
99
+ - Simpler mental model: "When X changes, update this specific DOM property"
100
+
101
+ **Concrete example:**
102
+
103
+ ```typescript
104
+ // In React - component re-executes on every count change
105
+ function ReactCounter() {
106
+ const [count, setCount] = useState(0);
107
+ console.log("Re-rendering!"); // Logs on every state change
108
+
109
+ // Need useCallback to prevent infinite re-renders
110
+ const increment = useCallback(() => setCount(c => c + 1), []);
111
+
112
+ return <div>{count} <button onClick={increment}>+</button></div>;
113
+ }
114
+
115
+ // In fun-web - component executes once
116
+ const FunWebCounter: Component<{ count: FunState<number> }> = (signal, props) => {
117
+ console.log("Mounting!"); // Logs once, never again
118
+
119
+ const display = h("div");
120
+ const button = h("button", {}, "+");
121
+
122
+ // No useCallback needed - this closure never goes stale
123
+ on(button, "click", () => props.count.mod(n => n + 1), signal);
124
+ bindProperty(display, "textContent", props.count, signal);
125
+
126
+ return h("div", {}, [display, button]);
127
+ };
128
+ ```
129
+
130
+ In React, clicking the button causes the entire component to re-execute. In fun-web, clicking the button just calls `count.mod()`, which triggers the `bindProperty` subscription to update `display.textContent`. The component function never runs again.
131
+
132
+ ### FunState - Reactive State with Subscriptions
133
+
134
+ `FunState<T>` provides reactive compositional state [@fun-land/fun-state](../fun-state):
135
+
136
+ ```typescript
137
+ const userState = funState({ name: "Alice", age: 30 });
138
+
139
+ // Get current value
140
+ userState.prop("name").get(); // "Alice"
141
+
142
+ // Update state
143
+ userState.prop("age").set(31);
144
+
145
+ // Subscribe to changes (cleaned up when signal aborts)
146
+ userState.prop("name").subscribe(signal, (name) => {
147
+ element.textContent = name; // runs when name changes
148
+ });
149
+ ```
150
+
151
+ ### Component Signature
152
+
153
+ Components are functions that receive an AbortSignal and props:
154
+
155
+ ```typescript
156
+ type Component<Props = {}> = (
157
+ signal: AbortSignal, // For cleanup
158
+ props: Props // Data (static or reactive)
159
+ ) => Element // Returns plain DOM element
160
+ ```
161
+
162
+ **Props can contain anything:** Static values, callbacks, reactive state, or any combination. There's no distinction between "props" and "state" - state is just data that happens to have `.get()` and `.set()` methods.
163
+
164
+ **Examples:**
165
+ ```typescript
166
+ // Static props only
167
+ const Static: Component<{ title: string }> = (signal, props) =>
168
+ h("h1", {}, props.title);
169
+
170
+ // Reactive state in props
171
+ const Counter: Component<{ count: FunState<number> }> = (signal, props) =>
172
+ h("div", {}, String(props.count.get()));
173
+
174
+ // Mix of static and reactive
175
+ const Dashboard: Component<{
176
+ onLogout: () => void;
177
+ user: FunState<User>;
178
+ settings: FunState<Settings>;
179
+ }> = (signal, props) => {
180
+ // props.onLogout is a static callback
181
+ // props.user is reactive state
182
+ // props.settings is reactive state
183
+ };
184
+ ```
185
+
186
+ ### Reactivity Patterns
187
+
188
+ **Use `bindProperty` for simple one-way bindings:**
189
+
190
+ ```typescript
191
+ const nameEl = h("div");
192
+ const nameState: FunState<string> = state.prop("user").prop("name");
193
+
194
+ bindProperty(nameEl, "textContent", nameState, signal);
195
+ // nameEl.textContent stays in sync with nameState
196
+ ```
197
+
198
+ **Use `on` for events:**
199
+
200
+ ```typescript
201
+ const button = h("button", {}, "Click me");
202
+ on(button, "click", (e: MouseEvent & { currentTarget: HTMLButtonElement }) => {
203
+ console.log(e.currentTarget.textContent);
204
+ }, signal);
205
+ ```
206
+
207
+ **Chain for two-way bindings:**
208
+
209
+ ```typescript
210
+ const input = on(
211
+ bindProperty(
212
+ h("input", { type: "text" }),
213
+ "value",
214
+ state.prop("name"),
215
+ signal
216
+ ),
217
+ "input",
218
+ (e) => state.prop("name").set(e.currentTarget.value),
219
+ signal
220
+ );
221
+ ```
222
+
223
+ **Use `.subscribe()` for complex logic:**
224
+
225
+ ```typescript
226
+ state.subscribe(signal, (s) => {
227
+ element.textContent = s.count > 100 ? "Max!" : String(s.count);
228
+ element.className = s.count > 100 ? "maxed" : "normal";
229
+ element.setAttribute("aria-label", `Count: ${s.count}`);
230
+ });
231
+ ```
232
+
233
+ ### Cleanup with AbortSignal
234
+
235
+ All subscriptions and event listeners require an AbortSignal. When the signal aborts, everything cleans up automatically:
236
+
237
+ ```typescript
238
+ const MyComponent: Component<{ state: FunState<State> }> = (signal, props) => {
239
+ const display = h("div");
240
+ const button = h("button");
241
+
242
+ // All three clean up when signal aborts
243
+ bindProperty(display, "textContent", props.state.prop("count"), signal);
244
+ on(button, "click", () => props.state.mod(increment), signal);
245
+ props.state.subscribe(signal, (s) => console.log("Changed:", s));
246
+
247
+ return h("div", {}, [display, button]);
248
+ };
249
+
250
+ const mounted = mount(MyComponent, { state }, container);
251
+ mounted.unmount(); // Aborts signal → everything cleans up
252
+ ```
253
+
254
+ For the most part you won't have to worry about the abort signal if you use the helpers provided.
255
+
256
+ ## Best Practices
257
+
258
+ **Prefer helpers over manual subscriptions:**
259
+
260
+ ```typescript
261
+ // ✅ Good
262
+ bindProperty(element, "textContent", state.prop("count"), signal);
263
+
264
+ // ❌ Avoid (when bindProperty works)
265
+ state.prop("count").subscribe(signal, (count) => {
266
+ element.textContent = String(count);
267
+ });
268
+ ```
269
+
270
+ **Use `on()` for type safety:**
271
+
272
+ ```typescript
273
+ // ✅ Good - types inferred
274
+ on(button, "click", (e) => {
275
+ e.currentTarget.disabled = true; // TypeScript knows it's HTMLButtonElement
276
+ }, signal);
277
+
278
+ // ❌ Avoid - loses type information
279
+ button.addEventListener("click", (e) => {
280
+ (e.currentTarget as HTMLButtonElement).disabled = true;
281
+ }, { signal });
282
+ ```
283
+
284
+ **Manual subscriptions for complex updates:**
285
+
286
+ ```typescript
287
+ // ✅ complex updates may need subscribe
288
+ state.subscribe(signal, (s) => {
289
+ // Multiple DOM updates based on complex conditions
290
+ if (s.status === "loading") {
291
+ spinner.style.display = "block";
292
+ button.disabled = true;
293
+ } else {
294
+ spinner.style.display = "none";
295
+ button.disabled = false;
296
+ }
297
+ });
298
+ ```
299
+
300
+ ## API Reference
301
+
302
+ ### DOM Utilities
303
+
304
+ #### `h`
305
+
306
+ Declaratively create an HTML Element with properties and children.
307
+
308
+ ```typescript
309
+ const div = h("div", { id: "app" }, [
310
+ h("h1", null, "Hello"),
311
+ h("input", { type: "text", value: "foo" })
312
+ ]);
313
+ ```
314
+
315
+ **Attribute conventions:**
316
+ - Dashed properties (`data-*`, `aria-*`) → `setAttribute()`
317
+ - Don't event bind with properties, use `on()`
318
+ - Everything else → property assignment
319
+
320
+ #### bindProperty
321
+ ```ts
322
+ <E extends Element, K extends keyof E>(
323
+ el: E,
324
+ key: K,
325
+ fs: FunState<E[K]>,
326
+ signal: AbortSignal
327
+ ): E
328
+ ```
329
+
330
+ Bind element property to state. Returns element for chaining.
331
+
332
+ ```typescript
333
+ const input: HTMLInputElement = h("input");
334
+ bindProperty(input, "value", state.prop("name"), signal);
335
+ // input.value syncs with state.name
336
+ ```
337
+
338
+ #### on
339
+ ```ts
340
+ <E extends Element, K extends keyof HTMLElementEventMap>(
341
+ el: E,
342
+ type: K,
343
+ handler: (ev: HTMLElementEventMap[K] & { currentTarget: E }) => void,
344
+ signal: AbortSignal
345
+ ): E
346
+ ```
347
+
348
+ Add type-safe event listener. Returns element for chaining.
349
+
350
+ ```typescript
351
+ on(h("button"), "click", (e) => {
352
+ // e.currentTarget is typed as HTMLButtonElement
353
+ e.currentTarget.disabled = true;
354
+ }, signal);
355
+ ```
356
+
357
+ #### keyedChildren
358
+ ```ts
359
+ <T extends { key: string }>(
360
+ parent: Element,
361
+ signal: AbortSignal,
362
+ list: FunState<T[]>,
363
+ renderRow: (row: {
364
+ signal: AbortSignal;
365
+ state: FunState<T>;
366
+ remove: () => void;
367
+ }) => Element
368
+ ): KeyedChildren<T>
369
+ ```
370
+
371
+ Render and reconcile keyed lists efficiently. Each row gets its own AbortSignal for cleanup and a focused state.
372
+
373
+ ```typescript
374
+ interface Todo {
375
+ key: string;
376
+ label: string;
377
+ done: boolean;
378
+ }
379
+
380
+ const todos: FunState<Todo[]> = funState([
381
+ { key: "a", label: "First", done: false }
382
+ ]);
383
+
384
+ keyedChildren(h("ul"), signal, todos, (row) => {
385
+ const li = h("li");
386
+
387
+ // row.state is a focused FunState<Todo> for this item
388
+ bindProperty(li, "textContent", row.state.prop("label"), row.signal);
389
+
390
+ // row.remove() removes this item from the list
391
+ const deleteBtn = h("button", { textContent: "Delete" });
392
+ on(deleteBtn, "click", row.remove, row.signal);
393
+
394
+ li.appendChild(deleteBtn);
395
+ return li;
396
+ });
397
+ ```
398
+
399
+ #### $ and $$ - DOM Query Utilities
400
+
401
+ Convenient shortcuts for `querySelector` and `querySelectorAll` with better TypeScript support.
402
+
403
+ **`$<T extends Element>(selector: string): T | undefined`**
404
+
405
+ Query a single element. Returns `undefined` instead of `null` if not found.
406
+
407
+ ```typescript
408
+ const button = $<HTMLButtonElement>("#submit-btn");
409
+ if (button) {
410
+ button.disabled = true;
411
+ }
412
+
413
+ const input = $<HTMLInputElement>(".name-input");
414
+ ```
415
+
416
+ **`$$<T extends Element>(selector: string): T[]`**
417
+
418
+ Query multiple elements. Returns a real Array (not NodeList) for better ergonomics.
419
+
420
+ ```typescript
421
+ const items = $$<HTMLDivElement>(".item");
422
+ items.forEach(item => item.classList.add("active"));
423
+
424
+ // Array methods work directly
425
+ const texts = $$(".label").map(el => el.textContent);
426
+ ```
427
+
428
+ #### Other utilities
429
+
430
+ All return the element for chaining:
431
+
432
+ ```typescript
433
+ text: (content: string | number) => (el: Element) => Element
434
+ attr: (name: string, value: string) => (el: Element) => Element
435
+ attrs: (obj: Record<string, string>) => (el: Element) => Element
436
+ addClass: (...classes: string[]) => (el: Element) => Element
437
+ removeClass: (...classes: string[]) => (el: Element) => Element
438
+ toggleClass: (className: string, force?: boolean) => (el: Element) => Element
439
+ append: (...children: Element[]) => (el: Element) => Element
440
+ pipeEndo: <T>(...fns: Array<(x: T) => T>) => (x: T) => T
441
+ ```
442
+
443
+ ### Mounting
444
+
445
+ #### mount
446
+ ```ts
447
+ <Props>(component: Component<Props>, props: Props, container: Element): MountedComponent
448
+ ```
449
+
450
+ Mount component to DOM and manage lifecycle. You probably only need to call this once in your app.
451
+
452
+ ```typescript
453
+ const state = funState({ count: 0 });
454
+ const mounted = mount(
455
+ Counter,
456
+ { label: "Clicks", state },
457
+ document.body
458
+ );
459
+
460
+ mounted.unmount(); // Cleanup
461
+ ```
462
+
463
+ **Returns:**
464
+ ```typescript
465
+ interface MountedComponent {
466
+ element: Element
467
+ unmount(): void
468
+ }
469
+ ```
470
+
471
+ ## Composition
472
+
473
+ Components compose by calling other components and passing focused state via props:
474
+
475
+ ```typescript
476
+ import { prop } from "@fun-land/accessor";
477
+
478
+ interface AppState {
479
+ user: UserData;
480
+ settings: Settings;
481
+ }
482
+
483
+ const App: Component<{ state: FunState<AppState> }> = (signal, props) =>
484
+ h("div", {}, [
485
+ UserProfile(signal, {
486
+ editable: true,
487
+ state: props.state.focus(prop<AppState>()("user")),
488
+ }),
489
+ SettingsPanel(signal, {
490
+ state: props.state.focus(prop<AppState>()("settings")),
491
+ })
492
+ ]);
493
+ ```
494
+
495
+ Focused states only trigger updates when their slice changes. Components can also create **local state** using `funState()` instead of receiving it via props - there's no distinction, state is just data.
496
+
497
+ ## Examples
498
+
499
+ See working examples:
500
+ - `examples/counter/counter.ts` - Basic reactivity and composition
501
+ - `examples/todo-app/todo-app.ts` - Keyed lists and form binding
502
+
503
+ ```bash
504
+ yarn build:examples
505
+ open examples/counter/index.html
506
+ open examples/todo-app/todo.html
507
+ ```
508
+
509
+ ## Status
510
+
511
+ **Experimental** - APIs may change.
512
+
513
+ ## License
514
+
515
+ MIT
@@ -0,0 +1,136 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <coverage generated="1768272411491" clover="3.2.0">
3
+ <project timestamp="1768272411491" name="All files">
4
+ <metrics statements="121" coveredstatements="118" conditionals="38" coveredconditionals="34" methods="37" coveredmethods="32" elements="196" coveredelements="184" complexity="0" loc="121" ncloc="121" packages="1" files="3" classes="3"/>
5
+ <file name="dom.ts" path="/Users/jethrolarson/develop/fun-land/packages/fun-web/src/dom.ts">
6
+ <metrics statements="113" coveredstatements="110" conditionals="38" coveredconditionals="34" methods="34" coveredmethods="29"/>
7
+ <line num="4" count="2" type="stmt"/>
8
+ <line num="18" count="2" type="stmt"/>
9
+ <line num="23" count="32" type="stmt"/>
10
+ <line num="26" count="32" type="cond" truecount="2" falsecount="0"/>
11
+ <line num="27" count="13" type="stmt"/>
12
+ <line num="28" count="18" type="cond" truecount="2" falsecount="0"/>
13
+ <line num="30" count="16" type="cond" truecount="4" falsecount="0"/>
14
+ <line num="32" count="3" type="stmt"/>
15
+ <line num="33" count="3" type="stmt"/>
16
+ <line num="34" count="13" type="cond" truecount="4" falsecount="0"/>
17
+ <line num="36" count="2" type="stmt"/>
18
+ <line num="39" count="11" type="stmt"/>
19
+ <line num="45" count="32" type="cond" truecount="2" falsecount="0"/>
20
+ <line num="46" count="17" type="stmt"/>
21
+ <line num="49" count="32" type="stmt"/>
22
+ <line num="55" count="2" type="stmt"/>
23
+ <line num="59" count="29" type="cond" truecount="2" falsecount="0"/>
24
+ <line num="60" count="12" type="stmt"/>
25
+ <line num="61" count="24" type="cond" truecount="2" falsecount="0"/>
26
+ <line num="62" count="22" type="cond" truecount="4" falsecount="0"/>
27
+ <line num="63" count="12" type="stmt"/>
28
+ <line num="65" count="10" type="stmt"/>
29
+ <line num="73" count="2" type="stmt"/>
30
+ <line num="74" count="2" type="stmt"/>
31
+ <line num="75" count="4" type="stmt"/>
32
+ <line num="76" count="4" type="stmt"/>
33
+ <line num="77" count="4" type="stmt"/>
34
+ <line num="83" count="2" type="stmt"/>
35
+ <line num="84" count="2" type="stmt"/>
36
+ <line num="85" count="3" type="stmt"/>
37
+ <line num="86" count="3" type="stmt"/>
38
+ <line num="87" count="3" type="stmt"/>
39
+ <line num="93" count="2" type="stmt"/>
40
+ <line num="94" count="2" type="stmt"/>
41
+ <line num="95" count="2" type="stmt"/>
42
+ <line num="96" count="2" type="stmt"/>
43
+ <line num="97" count="3" type="stmt"/>
44
+ <line num="99" count="2" type="stmt"/>
45
+ <line num="102" count="2" type="stmt"/>
46
+ <line num="109" count="4" type="stmt"/>
47
+ <line num="112" count="4" type="stmt"/>
48
+ <line num="113" count="1" type="stmt"/>
49
+ <line num="115" count="4" type="stmt"/>
50
+ <line num="121" count="2" type="stmt"/>
51
+ <line num="122" count="2" type="stmt"/>
52
+ <line num="123" count="4" type="stmt"/>
53
+ <line num="124" count="4" type="stmt"/>
54
+ <line num="125" count="4" type="stmt"/>
55
+ <line num="131" count="2" type="stmt"/>
56
+ <line num="132" count="2" type="stmt"/>
57
+ <line num="133" count="2" type="stmt"/>
58
+ <line num="134" count="2" type="stmt"/>
59
+ <line num="135" count="2" type="stmt"/>
60
+ <line num="141" count="2" type="stmt"/>
61
+ <line num="142" count="2" type="stmt"/>
62
+ <line num="143" count="6" type="stmt"/>
63
+ <line num="144" count="6" type="stmt"/>
64
+ <line num="145" count="6" type="stmt"/>
65
+ <line num="151" count="2" type="stmt"/>
66
+ <line num="152" count="2" type="stmt"/>
67
+ <line num="153" count="3" type="stmt"/>
68
+ <line num="154" count="4" type="stmt"/>
69
+ <line num="155" count="3" type="stmt"/>
70
+ <line num="162" count="2" type="stmt"/>
71
+ <line num="168" count="3" type="stmt"/>
72
+ <line num="169" count="3" type="stmt"/>
73
+ <line num="175" count="2" type="stmt"/>
74
+ <line num="176" count="2" type="stmt"/>
75
+ <line num="177" count="1" type="stmt"/>
76
+ <line num="178" count="3" type="stmt"/>
77
+ <line num="208" count="2" type="stmt"/>
78
+ <line num="218" count="5" type="stmt"/>
79
+ <line num="220" count="5" type="stmt"/>
80
+ <line num="221" count="5" type="stmt"/>
81
+ <line num="223" count="8" type="stmt"/>
82
+ <line num="225" count="8" type="stmt"/>
83
+ <line num="227" count="5" type="stmt"/>
84
+ <line num="230" count="5" type="stmt"/>
85
+ <line num="231" count="8" type="stmt"/>
86
+ <line num="233" count="8" type="stmt"/>
87
+ <line num="234" count="8" type="stmt"/>
88
+ <line num="235" count="8" type="stmt"/>
89
+ <line num="236" count="17" type="stmt"/>
90
+ <line num="237" count="17" type="cond" truecount="2" falsecount="0"/>
91
+ <line num="238" count="16" type="stmt"/>
92
+ <line num="239" count="16" type="stmt"/>
93
+ <line num="243" count="7" type="stmt"/>
94
+ <line num="244" count="7" type="cond" truecount="2" falsecount="0"/>
95
+ <line num="245" count="1" type="stmt"/>
96
+ <line num="246" count="1" type="stmt"/>
97
+ <line num="247" count="1" type="stmt"/>
98
+ <line num="252" count="7" type="stmt"/>
99
+ <line num="253" count="15" type="cond" truecount="2" falsecount="0"/>
100
+ <line num="254" count="9" type="stmt"/>
101
+ <line num="255" count="21" type="stmt"/>
102
+ <line num="256" count="9" type="stmt"/>
103
+ <line num="259" count="0" type="stmt"/>
104
+ <line num="261" count="9" type="stmt"/>
105
+ <line num="266" count="7" type="stmt"/>
106
+ <line num="267" count="7" type="stmt"/>
107
+ <line num="268" count="15" type="stmt"/>
108
+ <line num="269" count="15" type="stmt"/>
109
+ <line num="270" count="15" type="stmt"/>
110
+ <line num="271" count="15" type="cond" truecount="2" falsecount="0"/>
111
+ <line num="272" count="10" type="cond" truecount="4" falsecount="0"/>
112
+ <line num="278" count="5" type="stmt"/>
113
+ <line num="281" count="5" type="stmt"/>
114
+ <line num="284" count="5" type="stmt"/>
115
+ <line num="286" count="4" type="stmt"/>
116
+ <line num="289" count="2" type="stmt"/>
117
+ <line num="290" count="0" type="cond" truecount="0" falsecount="4"/>
118
+ <line num="292" count="2" type="stmt"/>
119
+ <line num="293" count="0" type="stmt"/>
120
+ </file>
121
+ <file name="mount.ts" path="/Users/jethrolarson/develop/fun-land/packages/fun-web/src/mount.ts">
122
+ <metrics statements="7" coveredstatements="7" conditionals="0" coveredconditionals="0" methods="2" coveredmethods="2"/>
123
+ <line num="23" count="1" type="stmt"/>
124
+ <line num="28" count="9" type="stmt"/>
125
+ <line num="29" count="9" type="stmt"/>
126
+ <line num="30" count="9" type="stmt"/>
127
+ <line num="32" count="9" type="stmt"/>
128
+ <line num="35" count="3" type="stmt"/>
129
+ <line num="36" count="3" type="stmt"/>
130
+ </file>
131
+ <file name="state.ts" path="/Users/jethrolarson/develop/fun-land/packages/fun-web/src/state.ts">
132
+ <metrics statements="1" coveredstatements="1" conditionals="0" coveredconditionals="0" methods="1" coveredmethods="1"/>
133
+ <line num="2" count="31" type="stmt"/>
134
+ </file>
135
+ </project>
136
+ </coverage>