Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
7d41d66
rough port of MVU HTML renderer from llm branch, now as a projector
disconcision Jul 20, 2025
44dc637
cleanup html proj
disconcision Jul 20, 2025
cdad701
HTML projectors can now update their own HTML. cleanup
disconcision Jul 20, 2025
8358acc
show rec type in ci as their internal alias
disconcision Jul 21, 2025
ef4efcb
Merge branch 'dev' into projector-html
disconcision Jul 23, 2025
fcf0754
Merge branch 'dev' into projector-html
disconcision Jul 24, 2025
346e9ac
Merge branch 'dev' into projector-html
disconcision Jul 24, 2025
e62a9a9
Merge branch 'dev' into projector-html
disconcision Oct 17, 2025
333da3c
Merge branch 'dev' into projector-html
disconcision Oct 27, 2025
c283aab
Merge branch 'dev' into projector-html
disconcision Jan 17, 2026
f3bf2c1
dev merge
disconcision Jan 17, 2026
0190b26
Merge branch 'dev' into projector-html
disconcision Jan 29, 2026
2d79966
merge fix
disconcision Jan 29, 2026
ba6f6a2
Merge branch 'dev' into projector-html
disconcision Feb 2, 2026
33e0dbb
Merge branch 'dev' into projector-html
disconcision Feb 3, 2026
f14d4de
Merge branch 'dev' into projector-html
disconcision Feb 4, 2026
af63b4d
merge fix
disconcision Feb 5, 2026
a2f8636
merge fix
disconcision Feb 5, 2026
424ad6a
Add comprehensive HazelHtml web app library plan
disconcision Feb 5, 2026
00f2fca
Implement Phase 1: Expanded HTML types and renderer
disconcision Feb 5, 2026
027bc53
Implement Phase 2: Command system for side effects
disconcision Feb 5, 2026
df84cf2
Implement Phase 3: Subscription system for event sources
disconcision Feb 5, 2026
29dd10d
Add App type for full applications (Phase 4)
disconcision Feb 5, 2026
0f5e527
Integrate Cmd execution into event handlers (Phase 5)
disconcision Feb 5, 2026
1e379dc
Update plan with implementation status
disconcision Feb 5, 2026
c77213a
Add subscription lifecycle management to HazelDOM
disconcision Feb 5, 2026
37592a8
Add App type detection to HTMLProj
disconcision Feb 5, 2026
e9f197a
Add implementation documentation
disconcision Feb 5, 2026
6beb1e0
Execute init_cmd when App first renders
disconcision Feb 5, 2026
6655659
Update docs: init_cmd now implemented, clarify error boundaries
disconcision Feb 5, 2026
d6e125a
Add error boundaries for graceful failure handling
disconcision Feb 5, 2026
9375fd3
Update docs: error boundaries now implemented
disconcision Feb 5, 2026
36aacd8
Expand HTMLProj.init to recognize all HTML constructors and App type
disconcision Feb 5, 2026
bfb5340
Add HazelHtml example programs
disconcision Feb 5, 2026
57c5ef6
Add App View sidebar panel (placeholder)
disconcision Feb 5, 2026
e39f390
Update plan with implementation status
disconcision Feb 5, 2026
a31e5a1
Add sidebar App View panel that renders HTML evaluation results
disconcision Feb 5, 2026
a23b717
Add resizable HTML projector with drag handle
disconcision Feb 5, 2026
a4e59f7
Wire inject for App View sidebar to enable interactive HTML
disconcision Feb 5, 2026
db60ca8
Add App type support to App View sidebar
disconcision Feb 5, 2026
096de62
Add error boundaries to App View sidebar
disconcision Feb 5, 2026
2c61bec
Update plan with completed implementation status
disconcision Feb 5, 2026
018235d
Fix arrow syntax in example programs (=> to ->)
disconcision Feb 5, 2026
ba0c45b
Fix O(n²) ConstructorMap performance for large sum types
disconcision Feb 5, 2026
2bd2045
Document elaboration performance investigation
disconcision Feb 5, 2026
9089f1d
EXPERIMENTAL: Add Typ.normalize memoization cache
disconcision Feb 5, 2026
4bb0f95
WIP: Performance instrumentation and MVU architecture changes
disconcision Feb 5, 2026
ccd604e
Implement Elm-style MVU architecture for HazelDOM
disconcision Feb 7, 2026
db39d06
Improve fallback rendering and add type annotations to examples
disconcision Feb 7, 2026
fe6f810
Fix all example programs: add fun keywords, update to MVU architecture
disconcision Feb 7, 2026
2befb5c
Elaboration perf: add === in meet, remove normalize cache, disable al…
disconcision Feb 8, 2026
d6556a0
Lazy normalize + post-eval statics benchmark
disconcision Feb 8, 2026
7c8c5fd
Optimize Typ.meet for large sum types: 6 incremental improvements
disconcision Feb 8, 2026
7b818d4
Minor cleanup: unthunk IdTag.temp, reformat builtins, update examples
disconcision Feb 8, 2026
5b1bf9e
MVU improvements: fix subscriptions, keyboard capture, sidebar cleanup
disconcision Feb 8, 2026
bce50ac
Fix post-eval statics performance: compact builtin types + meet fast …
disconcision Feb 8, 2026
016bcf3
MVU programs: rewrite with labeled tuples/sum types, fix runtime bugs…
disconcision Feb 8, 2026
44cd2ed
fmt
disconcision Feb 8, 2026
ad21a51
Add MVU architecture documentation
disconcision Feb 8, 2026
f23c0c8
HTML projector: Block placeholder with resizable drag
disconcision Feb 8, 2026
9b3355c
MVU runtime: make update_fn option, remove debug prints, update docs
disconcision Feb 9, 2026
cde3f7a
Add HTML projector ideas doc
disconcision Feb 9, 2026
7b0bb6b
merge fix
disconcision Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
862 changes: 862 additions & 0 deletions bench/bench.re

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions bench/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
(executable
(name bench)
(libraries haz3lcore)
(modes js)
(js_of_ocaml
(flags :standard --debuginfo --noinline --sourcemap)
(build_runtime_flags :standard --noinline))
(preprocess
(pps ppx_deriving.show)))
331 changes: 331 additions & 0 deletions docs/hazel-html-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
# HazelHtml Implementation Guide

This document describes the HazelHtml web app library as implemented on the `hazel-html` branch. It covers the architecture, types, and runtime components that enable building interactive web applications in Hazel.

## Overview

HazelHtml provides:
- A comprehensive set of HTML element and attribute types for Hazel
- A command system for side effects (focus, scroll, clipboard, delays)
- A subscription system for event sources (resize, keyboard, timers, animation frames)
- An App type for full application structure with init and subscriptions
- Integration with the HTML projector for live rendering

The library follows an Elm-inspired architecture adapted for Hazel's constraints (no type parameters yet).

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│ Hazel Program │
│ │
│ Plain Html: Div([Class("app")], [Text("Hello")]) │
│ │
│ Or App: ((init_html, init_cmd), subscriptions_fn) │
│ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ HTMLProj.re (Projector) │
│ - Detects App vs plain Html │
│ - Evaluates subscriptions function for App type │
│ - Creates HazelDOM.t context │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ HazelDOM.re │
│ - Renders Html ADT to Virtual_dom nodes │
│ - Manages subscription lifecycle (setup/cleanup) │
│ - Event handlers execute Cmd when result is (Html, Cmd) │
└─────────────────────────────────────────────────────────────┘
┌───────────────┼───────────────┐
▼ ▼ ▼
CmdRunner.re SubManager.re Virtual_dom
(executes Cmd) (manages Sub) (renders DOM)
```

## Types

All types are defined in `src/language/builtins/BuiltinsADT.re`.

### HTML Type

Recursive sum type with ~40 element constructors:

```
HTML =
// Text content
| Text(String)
| Bool(Bool) | Int(Int) | Float(Float) // Convenience renderers

// Structural
| Div(attrs, children) | Span(...) | P(...) | Pre(...) | Code(...) | Blockquote(...)

// Headings
| H1(...) | H2(...) | H3(...) | H4(...) | H5(...) | H6(...)

// Lists
| Ul(...) | Ol(...) | Li(...)

// Forms
| Form(...) | Label(...) | Input(attrs) | TextArea(attrs, content)
| Button(...) | Select(...) | Option(attrs, label)
| Checkbox(attrs) | Radio(attrs) | Range(attrs) // Legacy variants

// Links/Media
| A(...) | Img(attrs)

// Tables
| Table(...) | Thead(...) | Tbody(...) | Tr(...) | Th(...) | Td(...)

// Semantic
| Header(...) | Footer(...) | Nav(...) | Main(...) | Section(...) | Article(...) | Aside(...)

// Utility
| Br | Hr(attrs)

// Escape hatch
| Node(tagName, attrs, children) // For any HTML element
```

Where `attrs = List(Attr)` and `children = List(HTML)`.

### Attr Type

Sum type with ~45 attribute/event constructors:

```
Attr =
// Identity
| Id(String) | Class(String) | Classes(List(String))

// Properties
| Disabled(Bool) | Placeholder(String) | Value(String) | Checked(Bool)
| Selected(Bool) | ReadOnly(Bool) | Required(Bool) | AutoFocus(Bool)

// Links/Media
| Href(String) | Src(String) | Alt(String) | Title(String) | Target(String)

// Input specifics
| Type(String) | Name(String) | Min(String) | Max(String) | Step(String)
| MaxLength(Int) | Pattern(String)

// Layout
| Width(String) | Height(String) | ColSpan(Int) | RowSpan(Int)

// Styling
| Style(List((String, String))) // CSS as key-value pairs

// Data attributes
| Data(String, String) // data-{name}={value}

// Event handlers (self-modifying pattern)
| OnClick(HTML -> HTML)
| OnDoubleClick(HTML -> HTML)
| OnMouseEnter(HTML -> HTML) | OnMouseLeave(HTML -> HTML)
| OnMouseDown((HTML, MouseEvent) -> HTML)
| OnMouseUp((HTML, MouseEvent) -> HTML)
| OnMouseMove((HTML, MouseEvent) -> HTML)
| OnKeyDown((HTML, KeyEvent) -> HTML)
| OnKeyUp((HTML, KeyEvent) -> HTML)
| OnKeyPress((HTML, KeyEvent) -> HTML)
| OnInput((HTML, String) -> HTML)
| OnChange((HTML, String) -> HTML)
| OnFocus(HTML -> HTML) | OnBlur(HTML -> HTML)
| OnSubmit(HTML -> HTML)

// Generic fallbacks
| Create(String, String) // attr(name, value)
| BoolAttr(String, Bool)
```

### Event Types

Product types for event data:

```
KeyEvent = (key, code, ctrl, shift, alt, meta)
= (String, String, Bool, Bool, Bool, Bool)

MouseEvent = (clientX, clientY, button, ctrl, shift, alt, meta)
= (Float, Float, Int, Bool, Bool, Bool, Bool)
```

### Cmd Type

Recursive sum type for side effects:

```
Cmd =
| CmdNone // No effect
| CmdBatch(List(Cmd)) // Multiple effects
| Focus(String) // Focus element by ID
| Blur(String) // Blur element by ID
| ScrollIntoView(String) // Scroll element into view
| ScrollTo(String, Float, Float) // Scroll element to (x, y)
| CopyToClipboard(String) // Copy text to clipboard
| Delay(Float, HTML -> HTML) // Run transform after delay (ms)
| Log(String) // Console log
```

### Sub Type

Recursive sum type for event sources:

```
Sub =
| SubNone // No subscription
| SubBatch(List(Sub)) // Multiple subscriptions
| OnResize((HTML, Int, Int) -> HTML) // Window resize
| OnVisibilityChange((HTML, Bool) -> HTML) // Tab visibility
| OnDocumentKeyDown((HTML, KeyEvent) -> HTML) // Global keydown
| OnDocumentKeyUp((HTML, KeyEvent) -> HTML) // Global keyup
| Every(Float, (HTML, Float) -> HTML) // Interval timer
| AnimationFrame((HTML, Float) -> HTML) // Animation frame
```

### App Type

Product type for full applications:

```
App = ((HTML, Cmd), HTML -> Sub)
= (init, subscriptions)

where:
init = (initial_html, startup_cmd)
subscriptions = function from current HTML to Sub
```

## Self-Modifying Pattern

Since Hazel doesn't have type parameters yet, we use a "self-modifying" pattern where:
- The model IS the HTML tree
- Event handlers receive the current HTML and return new HTML
- Optionally, handlers can return `(HTML, Cmd)` to also trigger effects

Example:
```
let counter = Div([
OnClick(fun html ->
// html is the current state, return new state
Div([...], [Int(get_count(html) + 1)])
)
], [Int(0)])
```

When handlers return a tuple `(new_html, cmd)`, the command is executed after updating the model.

## Runtime Components

### HazelDOM.re

The core renderer that:
1. Converts Hazel HTML ADT to Virtual_dom nodes
2. Sets up event handlers that evaluate Hazel functions
3. Detects `(HTML, Cmd)` returns and executes commands via CmdRunner
4. Manages subscription lifecycle via global registry

Key type:
```reason
type t = {
model: DHExp.t,
inject: DHExp.t => Ui_effect.t(unit),
view_term: DHExp.t => Node.t,
projector_id: option(Id.t),
subscriptions: option(DHExp.t),
};
```

### CmdRunner.re

Interprets Cmd values as UI effects:
- `Focus`/`Blur` - calls DOM element methods
- `ScrollIntoView`/`ScrollTo` - manipulates scroll position
- `CopyToClipboard` - uses clipboard API
- `Delay` - schedules callback with setTimeout
- `Log` - writes to console

### SubManager.re

Manages subscription lifecycle:
- `subscribe()` - sets up event listeners, returns cleanup handles
- `cleanup()` - removes all listeners for a subscription set
- Handles window events, document keyboard, intervals, animation frames

### HTMLProj.re

The projector wrapper that:
1. Detects if model is App type vs plain HTML
2. For App type, extracts HTML and evaluates subscriptions function
3. Creates HazelDOM.t context with subscription info
4. Passes projector ID for subscription tracking

## File Structure

```
src/language/builtins/
BuiltinsADT.re # All types: HTML, Attr, Cmd, Sub, App, KeyEvent, MouseEvent

src/haz3lcore/projectors/
HazelDOM.re # HTML -> Virtual_dom renderer with Cmd/Sub integration
CmdRunner.re # Cmd interpreter
SubManager.re # Subscription manager
implementations/
HTMLProj.re # Projector with App detection
```

## Usage Examples

### Plain HTML
```
Div([Class("greeting")], [
H1([], [Text("Hello World")]),
Button([OnClick(fun _ -> Text("Clicked!"))], [Text("Click me")])
])
```

### With Commands
```
Button([
OnClick(fun html ->
(Div([], [Text("Focused!")]), Focus("my-input"))
)
], [Text("Focus input")])
```

### Full App
```
let app = (
// init: (HTML, Cmd)
(Div([Id("counter")], [Int(0)]), CmdNone),

// subscriptions: HTML -> Sub
fun html -> Every(1000.0, fun (h, timestamp) ->
Div([Id("counter")], [Int(get_count(h) + 1)])
)
)
```

## Known Limitations

1. **Subscription cleanup on removal**: When a projector is removed entirely (not just re-rendered), subscriptions may leak. Proper cleanup would require projector lifecycle hooks.

2. **AnimationFrame cleanup**: AnimationFrame subscriptions recursively request frames and currently can't be stopped.

3. **Error boundaries**: Runtime errors in event handlers are now caught and display inline error UI (red-bordered div with error message). Subscription callback errors are caught and logged to console. Infinite loops or stack overflows may still crash.

## Future Enhancements

When Hazel gains type parameters, the types could be parameterized over a message type for full Elm-style architecture:
```
Html(msg)
Attr(msg)
Cmd(msg)
Sub(msg)
```

This would enable separate model/view/update rather than the self-modifying pattern.
Loading