{"id":37095170,"url":"https://github.com/samhennessy/hlive","last_synced_at":"2026-01-14T11:43:55.511Z","repository":{"id":48663169,"uuid":"375924218","full_name":"SamHennessy/hlive","owner":"SamHennessy","description":"HLive is a server-side WebSocket based dynamic template-less view layer for Go.","archived":false,"fork":false,"pushed_at":"2023-12-08T17:22:21.000Z","size":867,"stargazers_count":98,"open_issues_count":2,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-06-20T16:34:11.649Z","etag":null,"topics":["go","golang","websocket"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SamHennessy.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-11T06:17:27.000Z","updated_at":"2024-06-05T16:08:16.000Z","dependencies_parsed_at":"2023-12-08T18:29:19.735Z","dependency_job_id":"bd784ecb-3e97-48c4-8028-8ea4c7250a92","html_url":"https://github.com/SamHennessy/hlive","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/SamHennessy/hlive","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamHennessy%2Fhlive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamHennessy%2Fhlive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamHennessy%2Fhlive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamHennessy%2Fhlive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SamHennessy","download_url":"https://codeload.github.com/SamHennessy/hlive/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SamHennessy%2Fhlive/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28419257,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T10:47:48.104Z","status":"ssl_error","status_checked_at":"2026-01-14T10:46:19.031Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","websocket"],"created_at":"2026-01-14T11:43:54.867Z","updated_at":"2026-01-14T11:43:55.496Z","avatar_url":"https://github.com/SamHennessy.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# HLive\n\nServer-side virtual DOM\n\nHLive is a server-side WebSocket based dynamic template-less view layer for Go.\n\nHLive is a fantastic tool for creating complex and dynamic browser-based user interfaces for developers who want to keep all the logic in Go.\n\nAll the power and data available on the backend with the responsive feel of a pure JavaScript solution. \n\nIt's a great use case for admin interfaces and internal company tools.\n\n## Notice\n\n**The first version of the API is under active development. Change is likely. Your feedback is welcome.**\n\nPlease help the project by building something and giving us your feedback.\n\n## Table of contents\n- [Quick Start Tutorial](#quick-start-tutorial)\n  * [Step 1: Static Page](#step-1--static-page)\n  * [Step 2: Interactive Page](#step-2--interactive-page)\n- [Examples](#examples)\n  * [Simple](#simple)\n    + [Click](#click)\n    + [Hover](#hover)\n    + [Diff Apply](#diff-apply)\n  * [Advanced](#advanced)\n    + [Animation](#animation)\n    + [Clock](#clock)\n    + [File Upload](#file-upload)\n    + [Initial Sync](#initial-sync)\n    + [Local Render](#local-render)\n    + [Session](#session)\n    + [To Do List](#to-do-list)\n    + [URL Parameters](#url-parameters)\n- [Concepts](#concepts)\n  * [Tag](#tag)\n  * [Attribute](#attribute)\n    + [CSS Classes](#css-classes)\n    + [Style Attribute](#style-attribute)\n  * [Tag Children](#tag-children)\n  * [Components](#components)\n    + [EventBinding](#eventbinding)\n    + [EventHandler](#eventhandler)\n  * [Node](#node)\n  * [Element](#element)\n  * [Page](#page)\n    + [HTML vs WebSocket](#html-vs-websocket)\n  * [PageSession](#pagesession)\n  * [PageServer](#pageserver)\n  * [Middleware](#middleware)\n  * [PageSessionStore](#pagesessionstore)\n  * [HTTP vs WebSocket Render](#http-vs-websocket-render)\n  * [Tree and Tree Copy](#tree-and-tree-copy)\n  * [WebSocket Render and Tree Diffing](#websocket-render-and-tree-diffing)\n  * [First WebSocket Render](#first-websocket-render)\n  * [AutoRender and Manuel Render](#autorender-and-manuel-render)\n  * [Local Render](#local-render-1)\n  * [Differ](#differ)\n  * [Render](#render)\n  * [HTML Type](#html-type)\n  * [JavaScript](#javascript)\n  * [Virtual DOM, Browser DOM](#virtual-dom--browser-dom)\n  * [Lifecycle](#lifecycle)\n- [Known Issues](#known-issues)\n  * [Invalid HTML](#invalid-html)\n  * [Browser Quirks](#browser-quirks)\n- [Inspiration](#inspiration)\n  * [Phoenix LiveView](#phoenix-liveview)\n  * [gomponents](#gomponents)\n  * [ReactJS and JSX](#reactjs-and-jsx)\n- [Similar Projects](#similar-projects)\n  * [GoLive](#golive)\n  * [live](#live)\n- [TODO](#todo)\n  * [API Change](#api-change)\n  * [Bugs](#bugs)\n  * [Internal improvements](#internal-improvements)\n    + [Groups](#groups)\n    + [Page Pipeline](#page-pipeline)\n    + [Other](#other)\n  * [Tests](#tests)\n  * [Docs](#docs)\n  * [Security](#security)\n  * [New Features/Improvements](#new-features-improvements)\n- [HHot](#hhot)\n  * [HHot Ideas](#hhot-ideas)\n  * [Older ideas](#older-ideas)\n    + [Serializable tree/component state](#serializable-tree-component-state)\n    + [Do more with the HTTP Server render](#do-more-with-the-http-server-render)\n    + [Multi file upload using WS and HTTP](#multi-file-upload-using-ws-and-http)\n    + [Visibility](#visibility)\n- [Contributing](#contributing)\n  * [Run tests](#run-tests)\n    + [Setup](#setup)\n    + [Run](#run)\n\n## Quick Start Tutorial\n\n### Step 1: Static Page\n\nImport HLive using the optional alias `l`:\n\n```go\npackage main\n\nimport l \"github.com/SamHennessy/hlive\"\n```\n\nLet's create our first page:\n\n```go\nfunc home() *l.Page {\n\tpage := l.NewPage()\n\tpage.DOM.Body.Add(\"Hello, world.\")\n\n\treturn page\n}\n```\n\nNext we use a `PageServer` to add it to an HTTP router:\n\n```go\nfunc main() {\n\thttp.Handle(\"/\", l.NewPageServer(home))\n\n\tlog.Println(\"Listing on :3000\")\n\n\tif err := http.ListenAndServe(\":3000\", nil); err != nil {\n\t\tlog.Println(\"Error: http listen and serve:\", err)\n\t}\n}\n```\n\nYour editor should add the extra imports `http` and `log` for you.\n\nYou can now run it, for example:\n```shell\ngo run ./tutorial/helloworld/helloworld.go\n```\n\nIn a browser go to http://localhost:3000 you should see this:\n\n![Hello world step 1](./_tutorial/helloworld/img/step1.png)\n\n### Step 2: Interactive Page\nHLive is all about interactive content. We're going to add a text input field to let us type our own hello message.\n\nWe need to replace our existing `home` function. We need a string to hold our message:\n\n```go\nfunc home() *l.Page {\n\tvar message string\n```\n\nNow we're going to create a `Component`. `Component`'s are HTML tags that can react to browser events. We are going to base our `Component` on the `input` HTML tag.\n\n```go\n\tinput := l.C(\"input\")\n```\n\nWe want to set the input to a text type. We do this adding a`Attrs` map to our `Component`.\n\n```go\n\tinput.Add(l.Attrs{\"type\": \"text\"})\n```\n\nHere we add an `EventBinding` to listen to \"keyup\" JavaScript events. When triggered, the handler function will be called. Our handler will update `message`. It does this by using the data passed in the `Event` parameter.\n\n```go\n\tinput.Add(l.On(\"keyup\", func(ctx context.Context, e l.Event) {\n\t\tmessage = e.Value\n\t}))\n```\n\nWe create a new `Page` like before:\n\n```go\n\tpage := l.NewPage()\n```\n\nHere we add our `input` to the body but first we wrap it in a `div` tag.\n\n```go\n\tpage.DOM.Body.Add(l.T(\"div\", input))\n```\n\nNext, we will display our message. Notice that we're passing `message` by reference. That's key for making this example work. We'll also add an \"hr\" tag to stop it being squashed todeather.\n\n```go\n\tpage.DOM.Body.Add(l.T(\"hr\"))\n\tpage.DOM.Body.Add(\"Hello, \", \u0026message)\n```\n\nFinally, we return the `Page` we created.\n```go\n\treturn page\n}\n```\n\nLet's see that all together, but this time I'm going to use some shortcuts. Can you spot the differences?\n\n```go\nfunc home() *l.Page {\n\tvar message string\n\n\tinput := l.C(\"input\",\n\t\tl.Attrs{\"type\": \"text\"},\n\t\tl.OnKeyUp(func(ctx context.Context, e l.Event) {\n\t\t\tmessage = e.Value\n\t\t}),\n\t)\n\n\tpage := l.NewPage()\n\tpage.DOM.Body.Add(\n\t\tl.T(\"div\", input),\n\t\tl.T(\"hr\"),\n\t\t\"Hello, \", \u0026message,\n\t)\n\n\treturn page\n}\n```\n\nRun it and type something into the input. The page should update to display what you typed.\n\n![Hello world step 2](./_tutorial/helloworld/img/step2.gif)\n\n## Examples\n\nThe examples can be run from the root of the project using `go run \u003cpath_to_example\u003e`. For example:\n\n```shell\ngo run _example/click/click.go\n```\n\n### Simple\n\n#### Click\n\n[_example/click/click.go](./_example/click/click.go)\n\nClick a button see a counter update.\n\nhttps://user-images.githubusercontent.com/119867/131120937-64091d27-3232-4820-ab20-e579c86cfb92.mp4\n\n#### Hover\n\n[_example/hover/hover.go](./_example/hover/hover.go)\n\nHover over an element and see another element change\n\n#### Diff Apply\n\n[_example/callback/callback.go](./_example/callback/callback.go)\n\nTrigger a Diff Apply event when a DOM change is applied in the browser. Use it to trigger server side logic.\n\n### Advanced\n\n#### Animation\n\n[_example/animation/animation.go](./_example/animation/animation.go)\n\nCreate a continuously changing animation by chaining Diff Apply callbacks.\n\n#### Clock\n\n[_example/clock/clock.go](./_example/clock/clock.go)\n\nPush browser DOM changes from the server without the need for a user to interact with the page.\n\n#### File Upload\n\n[_example/fileUpload/fileUpload.go](./_example/fileUpload/fileUpload.go)\n\nUse a file input to get information about a file before uploading it. Then trigger a file upload from the server when you're ready.\n\nThe file is uploaded via WebSocket as a binary (not base64 encoded) object.\n\n#### Initial Sync\n\n[_example/initialSync/initialSync.go](./_example/initialSync/initialSync.go)\n\nSome browsers, such as FireFox, will not clear data from form fields when the page is reloaded. To the user there is data in the field and if they submit a form they expect that data to be recognised. \n\nInitial sync is a client side process that will send this data to the server after a page refresh. You can check for this behavior in your event handlers. \n\nThis example also shows how to get multiple values from inputs that support that.\n\n#### Local Render\n\n[_example/localRender/localRender.go](./_example/localRender/localRender.go)\n\nBy default, all Components are rendered after each Event Binding that a user triggers. \n\nYou can disable this by turning Auto Render off for a component. You can then render that manually but this will rerender the whole page.\n\nIf you only want to re-render a single component, and it's children you can do that instead. It's easy to introduce subtle bugs when using this feature.\n\n#### Session\n\n[_example/session/session.go](./_example/session/session.go)\n\nAn example of how to implement a user session using middleware and cookies. It also shows our to pass data from middleware to Components.\n\nUsing middleware in HLive is just like any Go app.\n\n#### To Do List\n[_example/todo/todo.go](./_example/todo/todo.go)\n\nA simple To Do list app.\n\n#### URL Parameters\n\n[_example/urlParams/urlParams.go](./_example/urlParams/urlParams.go)\n\nPassing URL params to Components is not straightforward in HLive. Here is an example of how to do it. \n\nThis is due to the HLive having a two-step process of loading a page and Components are primarily designed to get data from Events.\n\n## Concepts\n\n### Tag\n\nA static HTML tag. A Tag has a name (e.g., an `\u003cp\u003e\u003c/p\u003e`'s name is `p`). A Tag can have zero or more Attributes. A Tag can have child Tags nested inside it. A Tag may be Void, which means it doesn't have a closing tag (e.g., `\u003chr\u003e`). Void tags can't have child Tags.\n\n### Attribute\n\nAn Attribute has a name and a value.  (e.g., `href=\"https://example.com\"` or `disabled=\"\"`).\n\n#### CSS Classes\n\nThe HLive implementation of Tag has an optional special way to work with the `class` attribute. These types are all designed to make toggling CSS classes on and off easy. \n\nHLive's `ClassBool` is a `map[string]bool` type. The key is a CSS class, and the value enables the class for rending if true. This allows you to turn a class on and off. (e.g. `l.ClassBool{\"foo\": true, \"bar\": true, \"fizz\": true}`). The order of the class names in a single `ClassBool` is NOT respected. If the order of class names is significant, you can add them as separate `ClassBool` elements, and the order will be respected. You can add a new `ClassBool` elements with the same class name, and the original `ClassBool` element will be updated.\n\nEven better is `Class`, this is a string type that converts into a CSSBool. (e.g. `l.Class(\"foo bar fizz\")`). The order of the class names is respected. Each class can still be turned off individually using a `ClassBool` of the `ClassOff` string type.\n\n`ClassList` and `ClassListOff` are string slices that will enable or disable respectively CSS classes. (e.g. `l.ClassList{\"foo\", \"bar\", \"fizz\"}`)\n\n#### Style Attribute\n\nThe HLive implementation of Tag has an optional special way to work with the `style` attribute.\n\nHLive's `Style` is a `map[string]interface{}` type. The key is the CSS style rule, and the value is the value of the rule. The value can be a `string` or `nil`. If `nil`, the style rule gets removed.\n\nThe order of the style rules in a single `Style` is NOT respected. If the order of rules is significant, you can add them as separate `Style` elements, and the order will be respected.\n\n### Tag Children\n\n`Tag` has `func GetNodes() *l.NodeGroup`. This will return can children a `Tag` has.\n\nThis function is called many times and not always when it's time to render. Calls to `GetNodes` must\nbe [deterministic](https://en.wikipedia.org/wiki/Deterministic_algorithm). If you've not made a change to the `Tag`\nthe output is expected to be the same.\n\nThis function should not get or change data. For example, no calls to a remote API or database should happen in this function.\n\n### Components\n\nA `Compnent` wraps a `Tag`. It adds the ability to bind events that primarily happens in the browser to itself.\n\n#### EventBinding\n\nAn `EventBinding` is a combination of an `EventType` (e.g., click, focus, mouseenter), with a `Component` and an `EventHandler`.\n\n#### EventHandler\n\nThe `EventHandler` is a `func(ctx context.Context, e Event)` type.\n\nThese handlers are where you can fetch data from remote APIs or databases.\n\nDepending on the `EventType` you'll have data in the `Event` parameter.\n\n### Node\n\nA Node is something that can be rendered into an HTML tag. For example, a string, `Tag`, or `Component`. An\n`Attribute` is not a Node as it can't be rendered to a complete HTML tag.\n\n### Element\n\nAn Element is anything associated with a `Tag` or `Component`. This means that in addition to nodes, `Attribute` and `EventBinding` are also Elements.\n\n### Page\n\nA `Page` is the root element in HLive. There will be a single page instance for a single connected user.\n\n`Page` has HTML5 boilerplate pre-defined. This boilerplate also includes HLive's JavaScript.\n\n#### HTML vs WebSocket\n\nWhen a user requests a page, there are two requests. First is the initial request that generates the pages HTML. Then the second request is to establish a WebSocket connection.\n\nHLive considers the initial HTML is can be though of as the Server Side Rendering phase (SSR). This SSR request will not be used when processing WebSocket requests. This render is a good candidate for use in a CDN.\n\nWhen an HLive SSR page is loaded in a browser, the HLive JavaScript library will kick into action.\n\nThe first thing the JavaScript will do is establish a WebSocket connection to the server. This connection is made using the same URL with `?hlive=1` added to the URL. Due to typical load balancing strategies, the server that HLive establishes a Websocket connection to may not be the one that generated the SSR Page.\n\n### PageSession\n\nWhen the JavaScript establishes the WebSocket connection, the backend will create a new session and send down the session id to the browser.\n\nA `PageSession` represents a single instance of a `Page`. There will be a single WebSocket connection to a `PageSession`.\n\n### PageServer\n\nThe `PageServer` is what handles incoming HTTP requests. It's an `http.Handler`, so it can be used in your router of choice. When `PageServer` receives a request, if the request has the `hlive=1` query parameter, it will start the WebSocket flow. It will create a new instance of your `Page`. It will then make a new `PageSession`. Finally, it will pass the request to `Page` `ServerWS` function.\n\nIf not, then it will create a new `Page`, generate a complete a SSR page render and return that and discard that `Page`.\n\n### Middleware\n\nIt's possible to wrap `PageServer` in middleware. You can add data to the context like normal. The context will be passed to your `Component`'s `Mount` function if it has one.\n\n### PageSessionStore\n\nTo manage your all the `PageSession`s `PageServer` uses a `PageSessionStore`. By default, each page gets its own `PageSessionStore`, but it's recommended that you have a single `PageSessionStore` that's shared by all your `Page`s on a server.\n\n`PageSessionStore` can control the number of active `PageSession`s you have at one time. This control can prevent your servers from becoming overloaded. Once the `PageSession` limit is reached, `PageSessionStore` will make incoming WebSocket requests wait for an existing connection to disconnect.\n\n### HTTP vs WebSocket Render\n\n`Mount` is not called on SSR requests but is called on WebSocket requests.\n\n### Tree and Tree Copy\n\nTree describes a Node and all it's child Nodes.\n\nTree copy is a critical process that takes your `Page`'s Tree and makes a simplified clone of it. Once done, the only elements in the cloned Tree are `Tag`s and `Attribute`s.\n\n### WebSocket Render and Tree Diffing\n\nWhen it's time to do a WebSocket render, no HTML is rendered *(1)*. What happens is a new Tree Copy is created from the `Page`. This Tree is compared to the Tree that's in that should be in the browser. The differences are calculated, and instructions are sent to the browser on updating its DOM with our new Tree.\n\n*(1) except Attributes, but that's just convenient data format.*\n\n### First WebSocket Render\n\nWhen a WebSocket connection is successfully established, we need to do 2 `Page` renders. The first is to duplicate what should be in the browser. This render will be creating a Tree Copy as if it were going to be an SSR render. This Tree is then set as the \"current\" Tree. Then a WebSocket Tree Copy is made. This copy will contain several attributes not present in the HTML Tree. Also, each `Component` in the Tree that implements `Mounter` will be called with the context, meaning the Tree may also have more detail based on any data fetched. This render will then be diffed against the \"current\" Tree and the diff instructions sent to the browser like normal.\n\nFor an initial, successful `Page` load there will be 3 renders, 2 HTML renders and a WebSocket render.\n\n### AutoRender and Manuel Render\n\nBy default, HLive's `Component` will do a render every time an `EventBinding` is triggered.\n\nThis behaviour can be turned off on `Component` by setting `AutoRender` to `false`.\n\nIf you set `AutoRender` to `false` you can manually trigger a WebSocket render by calling `hlive.Render(ctx context.Context)` with the context passed to your handler.\n\n### Local Render\n\nIf you want only to render a single `Component` and not the whole page, you can call `hlive.RenderComponent(ctx context.Context, comp Componenter)` you will also want to set any relevant `Component`s to `AutoRender` `false`.\n\n### Differ\n\nTODO: What is it and how does it work\n\n### Render\n\nTODO: What is it\n\n### HTML Type\n\nHLive's `HTML` type is a special `string` type that will render what you've set. One rule is that the HTML in `HTML` have a single root node.\n\n### JavaScript\n\nThe goal of HLive is not to require the developer to need to write any JavaScript. As such, we have unique solutions for things like giving fields focus.\n\nNothing is preventing the developer from adding their JavaScript. If JavaScript changes the DOM in the browser, you could cause HLive's diffing to stop working. This is also [true in libraries like ReactJS](https://reactjs.org/docs/integrating-with-other-libraries.html#integrating-with-dom-manipulation-plugins).\n\n### Virtual DOM, Browser DOM\n\nHLive is blind to what the actual state of the browser's DOM is. It assumes that it what it has set it to.\n\n### Lifecycle\n\nTODO\n\n## Known Issues\n\n### Invalid HTML\n\nIf you use invalid HTML typically by using HTML where you should not, the browser will ignore the HTML and not add it to the browsers DOM. If the element were something like a `span` tag then it may not be perceivable that it's happened. If this happens then the path finding for these tags, and it's children will not work or will work strangely. \n\nWe don't have HTML validation rules in HLive, so there is no way of warning you of this being the problem. \n\n### Browser Quirks\n\nBrowsers are complex things and sometimes act in unexpected ways. For example, if you have a table without a table body tag (`tbody`) some browsers will add a `tbody` to the DOM. This breaks HLives element path finding. Another example is that if you have multiple text nodes next to each other, some browsers will combine them. \n\nWe'll try and account for this where we can by mimicking the browser's behavior when doing a Tree Copy. We've done this be the text quirk but not the `tbody` quirk yet. \n\n\n\n## Inspiration\n\n### Phoenix LiveView\n\nFor the concept of server-side rendering for dynamic applications.\n\nhttps://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html\n\n### gomponents\n\nFor it's HTML API.\n\nhttps://github.com/maragudk/gomponents\n\n### ReactJS and JSX\n\nFor its component approach and template system.\n\nhttps://reactjs.org/\n\n## Similar Projects\n\n### GoLive\n[https://github.com/brendonmatos/golive](https://github.com/brendonmatos/golive)\n\nLive views for GoLang with reactive HTML over WebSockets\n\n### live\n\n[https://github.com/jfyne/live](https://github.com/jfyne/live)\n\nLive views and components for golang\n\n## TODO\n\n## v0.2.0\n\n- Race conditions in examples\n- Update docs based on API changes\n- Add SSR example\n\n### API Change\n\n- Add missing data from browser events, like:\n  - https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent\n  - https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent\n  - https://developer.mozilla.org/en-US/docs/Web/API/Touch_events\n  - https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent\n\n### Bugs\n\n- Need to reflect in the browser virtual DOM that a select option has become selected when a user selects it\n  - So that we can reset the selection (e.g. move dropdowns)\n- Can read a POST but can't pass POST data to a render (display errors)\n  - Makes Auth logins an issue\n  - Workaround it to go a redirect with an url param\n- Preempt disable on click prevents form submit in Chrome\n\n### Internal improvements\n\n#### Groups\n\n- Add the Grouper interface\n  - func GetGroup() []interface{}\n  - Add the NoneNodeElementsGroup\n\n#### Page Pipeline\n\n- HTTP request w, r\n\n#### Performance\n\n- Use binary for websocket\n  - Skips UTF8 processing\n  - Is this worth the trouble?\n- Alternative WebSocket lib?\n  - https://github.com/nhooyr/websocket\n- Add special logic for class tags\n  - Add, remove classes\n- Add special logic for style tags\n  - Only update property value if needed\n\n- Batch message sends and receives in the javascript (https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide)\n\n- Move file upload out of Page.js\n\n#### Other\n\n- Add log level to client side logging\n- Send config for debug, and log level down to client side\n  - Set via an attribute\n\n#### Can we make a hash of a simplified DOM tree?\n\n- If that page hash is not found in the cache then we need a fallback\n  - Force a browser reload with a new hash?\n- Need a way to know that the version of HLive has changed, if so need a hard page reload and cache bypass \n\n#### Add support for Wails\n- would be a JS binding for reading incoming messages what just blocks when waiting for a message\n- another binding sending messages\n\n### Tests\n\n- Add JavaScript test for multi child delete\n    - E.g.:\n      - d|d|doc|1\u003e1\u003e0\u003e0\u003e1\u003e2||\n      - d|d|doc|1\u003e1\u003e0\u003e0\u003e1\u003e3||\n- `Page.Close`\n\n- How well does [Alpine.JS](https://alpinejs.dev/) work with HLive\n  - https://dockyard.com/blogs/optimizing-user-experience-with-liveview\n\n#### Performance\n\n- Need a way to test performance improvement ideas\n- Why are large tables of data slow to page?\n  - It's faster to delete all the rows first\n  - Can we add a way for a component like List to inform tree copy not to bother doing a diff and just do a full HTML replacement\n  - If we check for `hid` and they are different, then do an HTML replace\n\n### Docs\n\n- Add initial page sync to concepts\n  - An input needs to have binding for this to work\n- Add one page one user to concepts\n- How to debug in browser\n- How on mount render order issues\n  - Try to update an element that has already been processed the diff will not be noticed\n  - Use the dirty tree error?\n- Logging\n- Plugins\n- Preempt pattern\n- Event bubbling \n- Prevent default\n- Stop propagation \n- Explain performance goals\n  - Explain why WASM is not a good fit for the goals\n- From the beginning tech intro - https://www.reddit.com/r/golang/comments/w5v4oe/comment/ihcm8i9/?utm_source=share\u0026utm_medium=web2x\u0026context=3\n- Page hooks\n\n### Security\n\n- Add a CSRF token\n  - https://github.com/gorilla/csrf\n  - Is this needed?\n\n### New Features/Improvements\n\n- Look for a CSS class to show on a failed reconnect\n  - Set current z-index higher than Bulma menu for default disconnect layer\n- Allow adding mount and unmount function as elements?\n\n- Add support for \"key\" to allow better diff logic for lists\n  - Use `hid`\n\n- Add a `func() *l.NodeGroup` value\n  - Reduce code count\n  - Does it solve any real issues\n\n- ComponentList\n  - Operations by ID\n    - Get by ID\n    - Remove By ID\n\n- User friendly HTTP error pages\n  - Display a request ID if it exits\n\n- Add can take a `func() string` this would be kept in the tree and re-run on each render\n  - Could be expensive\n\n#### Multi file upload using WS and HTTP\n\n- Need a count of files\n- Group them together in an event?\n- Make a channel?\n- File upload progress?\n\n#### Visibility\n\n- Is a component visible?\n- Trigger event when visible?\n- Scroll events\n  - Page position\n  - Viewport\n\n## Contributing\n\nContributions welcome\n\n### Run tests\n\n#### Setup\n\nInstall Play wright Go\n\n```shell\nmake install-test\n```\n\nor\n\n```shell\ngo run github.com/playwright-community/playwright-go/cmd/playwright install --with-deps\n```\n\n#### Run\n\n```shell\ngo test ./...\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamhennessy%2Fhlive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamhennessy%2Fhlive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamhennessy%2Fhlive/lists"}