diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
 # Revision history for hyperbole
 
+
+## 0.7.0 -- TODO
+
+Features:
+* Accept multipart form file uploads with good default security settings
+
+Improvements:
+* `trigger` and `pushEvent` now send to the client immediately instead of waiting for the action to finish
+* Added `cookieSecure` option to Session class
+* Deprecated `toActionInput` in favor of `inputValue` - Instead of partially applying a constructor, inputs are read with a function similar to forms
+* `Web.Hyperbole.Data.Param` was unnecessary and removed. Everything uses ToJSON and FromJSON as an intermediate encoding
+* moved OAuth2 to sub-package to allow for dependencies on cryptography libraries
+
+Breaking Changes:
+* Actions with input like `search`, `onInput` and `dropdown` should now use an action with no arguments. Get the value using `inputValue` instead
+* `ToParam` and `FromParam` are no longer used. Use the generic implementation of `ToJSON` and `FromJSON` instead
+
+Maintenance:
+* NIX support is fully up to date
+* Strict Typescript
+* [Many issues resolved](https://github.com/seanhess/hyperbole/issues?q=is%3Aissue%20state%3Aclosed)
+
+
+
 ## 0.6.0 -- 2026-01-15
 
 Improvements:
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 module Main where
 
 import Data.Text (Text)
-import Web.Atomic.CSS
+import Web.Atomic.CSS (border, (~))
 import Web.Hyperbole
 
 main :: IO ()
@@ -78,6 +78,7 @@
     build-depends:
         base
       , hyperbole
+      , atomic-css
       , text
 
     default-language: GHC2021
@@ -97,8 +98,8 @@
 <!--     <img src="https://github.com/seanhess/hyperbole/raw/main/docs/hackage.svg"> -->
 <!-- </a> -->
 
-* [Local Development](./docs/dev.md)
 * [Comparison with Similar Frameworks](./docs/comparison.md)
+* [Local Development](./docs/dev.md)
 * [Using NIX](./docs/nix.md)
 
 In the Wild
@@ -108,7 +109,7 @@
   <img alt="National Solar Observatory" src="https://nso1.b-cdn.net/wp-content/uploads/2020/03/NSO-logo-orange-text.png" width="400"/>
 </a>
 
-The NSO uses Hyperbole to manage Level 2 Data pipelines for the [DKIST telescope](https://nso.edu/telescopes/dki-solar-telescope/). It uses complex user interfaces, workers, databases, and more. [The entire codebase is open source](https://github.com/DKISTDC/level2/). 
+The NSO uses Hyperbole to manage Level 2 Data pipelines for the [DKIST telescope](https://nso.edu/telescopes/dki-solar-telescope/)
 
 
 
@@ -125,3 +126,7 @@
 * [Tushar Adhatrao](https://github.com/tusharad)
 * [Benjamin Thomas](https://github.com/benjamin-thomas)
 * [Adithya Obilisetty](https://github.com/adithyaov)
+* [Jens Krause](https://github.com/sectore)
+* [He Zhenxing](https://github.com/hezhenxing)
+* [fanshi1028](https://github.com/fanshi1028)
+* [jkasalt](https://github.com/jkasalt)
diff --git a/client/dist/hyperbole.js b/client/dist/hyperbole.js
--- a/client/dist/hyperbole.js
+++ b/client/dist/hyperbole.js
@@ -1,21 +1,15 @@
-"use strict";(()=>{var Ce="data-key",A=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},Re=function(e,n){return n?A(e,n)?(console.warn("[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed."),!1):!0:!1},y=function(e,n){var r=e.length,i=-1;if(r)for(;++i<r&&n(e[i],i)!==!1;);},Ve=function(e){var n={};y(e,function(i){var o=i.attributes[Ce];Re(n,o)&&(n[o]=i)});for(var r in n)return n},$=function(e,n,r){return e.node.insertBefore(n.node,r)},He=function(e,n){for(var r in e)n[r]=e[r]},ke=function(e){var n=e.trim().replace(/\s+</g,"<").replace(/>\s+/g,">"),r=new DOMParser,i=r.parseFromString(n,"text/html");return i.body};function Se(t,e){var n=t==null?null:typeof Symbol<"u"&&t[Symbol.iterator]||t["@@iterator"];if(n!=null){var r,i,o,s,c=[],a=!0,l=!1;try{if(o=(n=n.call(t)).next,e===0){if(Object(n)!==n)return;a=!1}else for(;!(a=(r=o.call(n)).done)&&(c.push(r.value),c.length!==e);a=!0);}catch(d){l=!0,i=d}finally{try{if(!a&&n.return!=null&&(s=n.return(),Object(s)!==s))return}finally{if(l)throw i}}return c}}function Y(t,e){return qe(t)||Se(t,e)||De(t,e)||Oe()}function qe(t){if(Array.isArray(t))return t}function De(t,e){if(t){if(typeof t=="string")return _(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);if(n==="Object"&&t.constructor&&(n=t.constructor.name),n==="Map"||n==="Set")return Array.from(t);if(n==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return _(t,e)}}function _(t,e){(e==null||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n<e;n++)r[n]=t[n];return r}function Oe(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
-In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var m={STRING:"string",INT:"number",BOOL:"boolean"},g={},S=function(e,n,r){return{attrName:e,propName:n,type:r}},Pe=[["style","cssText"],["class","className"]];y(Pe,function(t){var e=Y(t,2),n=e[0],r=e[1];g[n]=S(n,r||n,m.STRING)});var Ue=["autofocus","draggable","hidden","checked","multiple","muted","selected"];y(Ue,function(t){g[t]=S(t,t,m.BOOL)});var Be=[["tabindex","tabIndex"]];y(Be,function(t){var e=Y(t,2),n=e[0],r=e[1];g[n]=S(n,r,m.INT)});var I={xlink:{prefix:"xlink:",resource:"http://www.w3.org/1999/xlink"},xml:{prefix:"xml:",resource:"http://www.w3.org/XML/1998/namespace"}},x=function(e,n,r,i){switch(n){case m.STRING:{r===g.style.propName?i===null?e.style[r]="":e.style[r]=i:i===null?e[r]="":e[r]=i;break}case m.INT:{if(i===null){var o=r.toLowerCase();e.removeAttribute(o)}else if(i==="0")e[r]=0;else if(i==="-1")e[r]=-1;else{var s=parseInt(i,10);isNaN(s)||(e[r]=s)}break}case m.BOOL:{["","true"].indexOf(i)<0?e[r]=!1:e[r]=!0;break}}},Ke=function(e,n){y(n,function(r){if(A(g,r)){var i=g[r];x(e.node,i.type,i.propName,null)}else r in e.node&&x(e.node,m.STRING,r,null),e.node.removeAttribute(r);delete e.attributes[r]})},je=function(e,n){for(var r in n){var i=n[r];if(e.attributes[r]=i,A(g,r)){var o=g[r];x(e.node,o.type,o.propName,i);continue}if(r.startsWith(I.xlink.prefix)){e.node.setAttributeNS(I.xlink.resource,r,i);continue}if(r.startsWith(I.xml.prefix)){e.node.setAttributeNS(I.xml.resource,r,i);continue}r in e.node&&x(e.node,m.STRING,r,i),e.node.setAttribute(r,i||"")}},Fe=function(e,n){for(var r in g){var i=g[r],o=i.propName,s=e.getAttribute(r);r===g.style.attrName?n[r]=e.style[o]:typeof s=="string"&&(n[r]=s)}},We=function(e){return Array.prototype.reduce.call(e.attributes,function(n,r){return A(g,r.name)||(n[r.name]=r.value),n},{})},Ge=function(e){var n=We(e);return Fe(e,n),n},Qe=function(e,n){var r=[],i={};for(var o in n.attributes){var s=n.attributes[o],c=e.attributes[o];s!==c&&typeof c>"u"&&r.push(o)}for(var a in e.attributes){var l=n.attributes[a],d=e.attributes[a];l!==d&&typeof d<"u"&&(i[a]=d)}Ke(n,r),je(n,i)},Je="data-key";function $e(t,e,n){var r=t.children.length,i=e.children.length;if(!(!r&&!i)){var o=Ve(e.children),s=Array(r);o!==void 0?y(t.children,function(l,d){var f=e.node.childNodes,u=l.attributes[Je];if(Object.prototype.hasOwnProperty.call(o,u)){var p=o[u];Array.prototype.indexOf.call(f,p.node)!==d&&$(e,p,f[d]),s[d]=p,delete o[u],n(l,s[d])}else $(e,l,f[d]),s[d]=l}):y(t.children,function(l,d){var f=e.children[d];typeof f<"u"?(n(l,f),s[d]=f):(e.node.appendChild(l.node),s[d]=l)}),e.children=s;var c=e.node.childNodes.length,a=c-r;if(a>0)for(;a>0;)e.node.removeChild(e.node.childNodes[c-1]),c--,a--}}var z=function t(e,n,r){if(!(!e||!n)){r=r||n.node.parentNode;var i=e.content&&e.content!==n.content;if(e.type!==n.type||i)return r.replaceChild(e.node,n.node),He(e,n);Qe(e,n),$e(e,n,t)}};var q=function t(e){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;typeof e=="string"&&(e=ke(e));var r=e.tagName==="BODY",i=e.childNodes,o=i?i.length:0;if(r){if(o>1)throw new Error("[OmDomDom]: Your element should not have more than one root node.");if(o===0)throw new Error("[OmDomDom]: Your element should have at least one root node.");return t(i[0])}var s=e.nodeType===3?"text":e.nodeType===8?"comment":e.tagName.toLowerCase(),c=n||s==="svg",a=e.nodeType===1?Ge(e):{},l=o>0?null:e.textContent,d=Array(o);return y(i,function(f,u){d[u]=t(f,c)}),{type:s,attributes:a,children:d,content:l,node:e,isSVGContext:c}};function D(t,e){var n=[];for(var r of e){let i=t(r);if(i)n.push(i);else break}return n}function X(t,e){let n=0;for(;n<e.length&&t(e[n]);)n++;return e.slice(n)}function N(t){return t.map(e=>e.key+": "+e.value).join(`
-`)}function L(t){return{cookies:t.filter(e=>e.key=="Cookie").map(e=>e.value),error:E("Error",t),query:E("Query",t),pageTitle:E("PageTitle",t),events:Z("Event",t).map(P),actions:Z("Trigger",t).map(U)}}function O(t){let e=D(te,t.trim().split(`
-`));return L(e)}function E(t,e){return e.find(n=>n.key==t)?.value}function Z(t,e){return e.filter(n=>n.key==t).map(n=>n.value)}function ee(t){let e=t.split(`
-`),n=e[0],r=D(te,e.slice(1)),i=X(o=>o=="",e.slice(r.length+1));return{command:n,metas:r,rest:i}}function te(t){let e=t.match(/^(\w+)\: (.*)$/);if(e)return{key:e[1],value:e[2]}}function P(t){let[e,n]=ne(t);return{name:e,detail:JSON.parse(n)}}function U(t){let[e,n]=ne(t);return[e,n]}function ne(t){let e=t.indexOf("|");if(e===-1){let n=new Error("Bad Encoding, Expected Segment");throw n.message=t,n}return[t.slice(0,e),t.slice(e+1)]}function ie(t,e,n,r,i){let o=[{key:"Cookie",value:decodeURI(document.cookie)},{key:"Query",value:window.location.search}];return{viewId:t,action:e,state:n,requestId:r,meta:o,form:_e(i)}}function _e(t){if(!t)return;let e=new URLSearchParams;return t.forEach((n,r)=>{e.append(r,n)}),e}function oe(t){let e=["|ACTION|","ViewId: "+t.viewId,"Action: "+t.action];return t.state&&e.push("State: "+t.state),e.push("RequestId: "+t.requestId),[e.join(`
-`),N(t.meta)].join(`
-`)+Ye(t.form)}function Ye(t){return t?`
-
-`+t:""}var ze=0;function se(){return{requestId:++ze,isCancelled:!1}}function B(t,e){return t+" "+Xe(e)}function Xe(t){return t==""?"|":t.replace(/_/g,"\\_").replace(/\s+/g,"_")}var Ze=window.location.protocol==="https:"?"wss:":"ws:",ae=`${Ze}//${window.location.host}${window.location.pathname}`,C=class{constructor(e=ae){this.hasEverConnected=!1;this.isConnected=!1;this.reconnectDelay=0;this.queue=[];this.events=new EventTarget;let n=new WebSocket(e);this.socket=n}connect(e=ae,n=!1){let r=n?new WebSocket(e):this.socket;this.socket=r;function i(s){console.error("Connect Error",s)}function o(s){console.error("Socket Error",s)}r.addEventListener("error",i),r.addEventListener("open",s=>{console.log("Websocket Connected"),this.hasEverConnected&&document.dispatchEvent(new Event("hyp-socket-reconnect")),this.isConnected=!0,this.hasEverConnected=!0,this.reconnectDelay=1e3,r.removeEventListener("error",i),r.addEventListener("error",o),document.dispatchEvent(new Event("hyp-socket-connect")),this.runQueue()}),r.addEventListener("close",s=>{console.log("CLOSE SOCKET"),this.isConnected&&document.dispatchEvent(new Event("hyp-socket-disconnect")),this.isConnected=!1,r.removeEventListener("error",o),this.hasEverConnected&&(console.log("Reconnecting in "+this.reconnectDelay/1e3+"s"),setTimeout(()=>this.connect(e,!0),this.reconnectDelay)),r.removeEventListener("error",o)}),r.addEventListener("message",s=>this.onMessage(s))}async sendAction(e){if(this.isConnected){let n=oe(e);this.socket.send(n)}else this.queue.push(e)}runQueue(){let e=this.queue.pop();e&&(console.log("runQueue: ",e),this.sendAction(e),this.runQueue())}onMessage(e){let{command:n,metas:r,rest:i}=ee(e.data),o=parseInt(s("RequestId"),0);function s(u){let p=E(u,r);if(!p)throw new R("Missing Required Metadata: "+u,e.data);return p}function c(u){let p=s("ViewId"),M=s("Action");return{requestId:o,targetViewId:void 0,viewId:p,action:M,meta:L(r),body:u.join(`
-`)}}function a(u){let p=c(u);return p.targetViewId=E("TargetViewId",r),p}function l(u){let p=u[0];return{requestId:o,meta:L(r),url:p}}function d(u){let{requestId:p,meta:M,viewId:V,action:H}=c(u),[k,Le]=U(s("Trigger"));return{requestId:p,meta:M,viewId:V,action:H,targetViewId:k,targetAction:Le}}function f(u){let{requestId:p,meta:M,viewId:V,action:H}=c(u),k=P(s("Event"));return{requestId:p,meta:M,viewId:V,action:H,event:k}}switch(n){case"|UPDATE|":return this.dispatchEvent(new CustomEvent("update",{detail:a(i)}));case"|RESPONSE|":return this.dispatchEvent(new CustomEvent("response",{detail:c(i)}));case"|REDIRECT|":return this.dispatchEvent(new CustomEvent("redirect",{detail:l(i)}));case"|TRIGGER|":return this.dispatchEvent(new CustomEvent("trigger",{detail:d(i)}));case"|EVENT|":return this.dispatchEvent(new CustomEvent("event",{detail:f(i)}));default:throw new R("Unknown Server Command: "+n,e.data)}}addEventListener(e,n){this.events.addEventListener(e,n)}dispatchEvent(e){this.events.dispatchEvent(e)}disconnect(){this.isConnected=!1,this.hasEverConnected=!1,this.socket.close()}},R=class extends Error{constructor(e,n){super(e+`
-`+n),this.name="ProtocolError"}};function K(t,e=100,n={}){if(typeof t!="function")throw new TypeError(`Expected the first parameter to be a function, got \`${typeof t}\`.`);if(e<0)throw new RangeError("`wait` must not be negative.");if(typeof n=="boolean")throw new TypeError("The `options` parameter must be an object, not a boolean. Use `{immediate: true}` instead.");let{immediate:r}=n,i,o,s,c,a;function l(){let u=i,p=o;return i=void 0,o=void 0,a=t.apply(u,p),a}function d(){let u=Date.now()-c;u<e&&u>=0?s=setTimeout(d,e-u):(s=void 0,r||(a=l()))}let f=function(...u){if(i&&this!==i&&Object.getPrototypeOf(this)===Object.getPrototypeOf(i))throw new Error("Debounced method called with different contexts of the same prototype.");i=this,o=u,c=Date.now();let p=r&&!s;if(s||(s=setTimeout(d,e)),p)return a=l(),a};return Object.defineProperty(f,"isPending",{get(){return s!==void 0}}),f.clear=()=>{s&&(clearTimeout(s),s=void 0,i=void 0,o=void 0)},f.flush=()=>{s&&f.trigger()},f.trigger=()=>{a=l(),f.clear()},f}var T=t=>t?.runAction!==void 0;function j(t){let e=new Event("hyp-content",{bubbles:!0});t.dispatchEvent(e)}function F(t,e){t.querySelectorAll("[id]").forEach(n=>{n.runAction=function(r){return e(n,r)},n.concurrency=n.dataset.concurrency||"Drop",n.cancelActiveRequest=function(){n.activeRequest&&!n.activeRequest?.isCancelled&&(n.activeRequest.isCancelled=!0)},j(t)})}function ce(t){de("keydown",t)}function ue(t){de("keyup",t)}function de(t,e){document.addEventListener(t,function(n){if(!(n.target instanceof HTMLElement)){console.warn("listenKeyEvent received event with non HTMLElment as EventTarget: %o",n);return}let r=n.target,i="on"+t+n.key,o=r.dataset[i];if(!o)return;n.preventDefault();let s=w(r);if(!s){console.error("Missing target: ",r);return}e(s,o)})}function le(t,e){document.addEventListener(t,function(n){if(!(n.target instanceof HTMLElement)){console.warn("listenBubblingEvent received an event with non HTMLElment as EventTarget: %o",n);return}let i=n.target.closest("[data-on"+t+"]");if(!i)return;n.preventDefault();let o=w(i);if(!o){console.error("Missing target: ",i);return}let s=i.dataset["on"+t];if(s===void 0){console.error("Missing action: ",i,t);return}e(o,s)})}function pe(t){le("click",t)}function fe(t){le("dblclick",t)}function ge(t){document.addEventListener("hyp-load",function(e){let n=e.detail.onLoad,r=e.detail.target;t(r,n)}),document.addEventListener("hyp-mouseenter",function(e){let n=e.detail.onMouseEnter,r=e.detail.target;t(r,n)}),document.addEventListener("hyp-mouseleave",function(e){let n=e.detail.onMouseLeave,r=e.detail.target;t(r,n)})}function W(t){t.querySelectorAll("[data-onload]").forEach(e=>{let n=parseInt(e.dataset.delay||"")||0,r=e.dataset.onload;setTimeout(()=>{let i=w(e);if(e.dataset.onload!=r)return;let o=new CustomEvent("hyp-load",{bubbles:!0,detail:{target:i,onLoad:r}});e.dispatchEvent(o)},n)})}function G(t){t.querySelectorAll("[data-onmouseenter]").forEach(e=>{let n=e.dataset.onmouseenter,r=J(e);e.onmouseenter=()=>{let i=new CustomEvent("hyp-mouseenter",{bubbles:!0,detail:{target:r,onMouseEnter:n}});e.dispatchEvent(i)}})}function Q(t){t.querySelectorAll("[data-onmouseleave]").forEach(e=>{let n=e.dataset.onmouseleave,r=J(e);e.onmouseleave=()=>{let i=new CustomEvent("hyp-mouseleave",{bubbles:!0,detail:{target:r,onMouseLeave:n}});e.dispatchEvent(i)}})}function ve(t){document.addEventListener("change",function(e){if(!(e.target instanceof HTMLElement)){console.warn("listenChange received an event with non HTMLElment as EventTarget: %o",e);return}let r=e.target.closest("[data-onchange]");if(!r)return;if(e.preventDefault(),r.value===null){console.error("Missing input value:",r);return}let i=w(r);if(!i){console.error("Missing target: listenChange");return}if(!r.dataset.onchange){console.error("Missing onchange: ",r);return}let o=B(r.dataset.onchange,r.value);t(i,o)})}function me(t,e){document.addEventListener("input",function(n){if(!(n.target instanceof HTMLElement)){console.warn("listenInput received an event with non HTMLElment as EventTarget: %o",n);return}let i=n.target.closest("[data-oninput]");if(!i)return;let o=parseInt(i.dataset.delay||"")||250;o<250&&console.warn("Input delay < 250 can result in poor performance."),n.preventDefault();let s=w(i);if(!s){console.error("Missing target: ",i);return}t(s),i.debouncedCallback||(i.debouncedCallback=K(()=>{if(!i.dataset.oninput){console.error("Missing onInput: ",i);return}let c=B(i.dataset.oninput,i.value);e(s,c)},o)),i.debouncedCallback()})}function ye(t){document.addEventListener("submit",function(e){if(!(e.target instanceof HTMLFormElement)){console.warn("listenFormSubmit received an event with non HTMLElment as EventTarget: %o",e);return}let n=e.target;if(!n.dataset.onsubmit){console.error("Missing onSubmit: ",n);return}e.preventDefault();let r=w(n),i=new FormData(n);if(!r){console.error("Missing target: ",n);return}t(r,n.dataset.onsubmit,i)})}function Ne(t){return t.closest("[data-target]")?.dataset.target||t.closest("[id]")?.id}function w(t){let e=J(t);if(!T(e)){console.error("Non HyperView target: ",e);return}return e}function J(t){let e=Ne(t),n=e&&document.getElementById(e);if(!n){console.error("Cannot find target: ",e,t);return}return n}function he(t){if(t!=et()){t!=""&&(t="?"+t);let e=location.pathname+t;window.history.replaceState({},"",e)}}function et(){let t=window.location.search;return t.startsWith("?")?t.substring(1):t}function Ee(t){let n=new DOMParser().parseFromString(t,"text/html"),r=n.querySelector("style");return{content:n.querySelector("div"),css:r}}var we={name:"web-ui",version:"0.6.0",description:"Development -----------",main:"index.js",directories:{client:"client"},scripts:{build:"esbuild src/index.ts --bundle --minify --sourcemap --outfile=dist/hyperbole.js",dev:"esbuild src/index.ts --bundle --minify --sourcemap --watch=forever --outfile=dist/hyperbole.js",check:"tsc --noEmit --preserveWatchOutput --watch"},author:"",license:"ISC",devDependencies:{esbuild:"^0.28.0",typescript:"^6.0.3"},dependencies:{omdomdom:"^0.3.2",debounce:"^3.0.0"}};console.log("Hyperbole "+we.version+"b");var b,be=new Set;async function v(t,e,n){if(t.activeRequest&&!t.activeRequest?.isCancelled&&t.concurrency=="Drop"){console.warn("Drop action overlapping with active request ("+t.activeRequest+")",e);return}t._timeout=window.setTimeout(()=>{t.classList.add("hyp-loading")},100);let r=t.dataset.state,i=se(),o=ie(t.id,e,r,i.requestId,n);t.activeRequest=i,h.sendAction(o)}function nt(t){Ae(t.targetViewId,t.targetAction)}function rt(t){let e=document.getElementById(t.viewId);xe(t.event,e)}function it(t){console.log("REDIRECT",t),Te(t.meta.cookies??[]),window.location.href=t.url}function ot(t){let e=Me(t);e&&(delete e.activeRequest,clearTimeout(e._timeout),e.classList.remove("hyp-loading"))}function Me(t){let e=t.targetViewId||t.viewId,n=document.getElementById(e);if(!T(n)){console.error("Missing Update HyperView Target: ",e,t);return}if(n.activeRequest?.requestId&&t.requestId<n.activeRequest.requestId)return console.warn("Ignore Stale Action ("+t.requestId+") vs ("+n.activeRequest.requestId+"): "+t.action),n;if(n.activeRequest?.isCancelled)return console.warn("Cancelled request",n.activeRequest?.requestId),delete n.activeRequest,n;let r=Ee(t.body);if(!r.content)return console.error("Empty Response!",t.body),n;at(r.css);let i=q(n),o=q(r.content),s=o.attributes;if(!t.meta.error&&s.id!=n.id){console.error("Mismatched ViewId in update - ",s.id," target:",n.id);return}let c=s["data-state"];o.attributes=i.attributes,z(o,i);let a=document.getElementById(n.id);return a?(j(a),c===void 0||c=="()"?delete a.dataset.state:a.dataset.state=c,Ie(t.meta,a),Te(t.meta.cookies??[]),W(a),G(a),Q(a),st(a),F(a,v),n):(console.warn("Target Missing: ",n.id),n)}function Te(t){t.forEach(e=>{console.log("SetCookie: ",e),document.cookie=e})}function Ie(t,e){t.query!=null&&he(t.query),t.pageTitle!=null&&(document.title=t.pageTitle),t.events?.forEach(n=>{xe(n,e)}),t.actions?.forEach(([n,r])=>{Ae(n,r)})}function xe(t,e){setTimeout(()=>{let n=new CustomEvent(t.name,{bubbles:!0,detail:t.detail});(e||document).dispatchEvent(n)},10)}function Ae(t,e){setTimeout(()=>{let n=window.Hyperbole?.hyperView(t);n&&v(n,e)},10)}function st(t){let e=t.querySelector("[autofocus]");e?.focus&&e.focus(),t.querySelectorAll("input[value]").forEach(n=>{let r=n.getAttribute("value");r!==null&&(n.value=r)}),t.querySelectorAll("input[type=checkbox]").forEach(n=>{let r=n.dataset.checked=="True";n.checked=r})}function at(t){if(!t)return;let e=t.sheet?.cssRules;if(e)for(let n=0;n<e.length;n++){let r=e.item(n);r&&be.has(r.cssText)==!1&&b.sheet&&(b.sheet.insertRule(r.cssText),be.add(r.cssText))}}function ct(){let t=O(document.getElementById("hyp.metadata")?.innerText??"");Ie(t,null);let e=document.body.querySelector("style");e!==null?b=e:(console.warn("rootStyles missing from page, creating..."),b=document.createElement("style"),b.type="text/css",document.body.appendChild(b)),ge(async function(r,i){v(r,i)}),W(document.body),G(document.body),Q(document.body),F(document.body,v),pe(async function(r,i){v(r,i)}),fe(async function(r,i){v(r,i)}),ce(async function(r,i){v(r,i)}),ue(async function(r,i){v(r,i)}),ye(async function(r,i,o){v(r,i,o)}),ve(async function(r,i){v(r,i)});function n(r){r.concurrency=="Replace"&&r.cancelActiveRequest()}me(n,async function(r,i){v(r,i)})}document.addEventListener("DOMContentLoaded",ct);var h=new C;h.connect();h.addEventListener("update",t=>{Me(t.detail)});h.addEventListener("response",t=>ot(t.detail));h.addEventListener("redirect",t=>it(t.detail));h.addEventListener("trigger",t=>nt(t.detail));h.addEventListener("event",t=>rt(t.detail));window.Hyperbole={runAction:v,parseMetadata:O,action:function(t,...e){return e.reduce((n,r)=>n+" "+JSON.stringify(r),t)},hyperView:function(t){let e=document.getElementById(t);if(!T(e)){console.error("Element id="+t+" was not a HyperView");return}return e},socket:h};})();
-/*! Bundled license information:
+(function(){var e=`data-key`,t=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n=function(e,n){return n?t(e,n)?(console.warn(`[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.`),!1):!0:!1},r=function(e,t){var n=e.length,r=-1;if(n)for(;++r<n&&t(e[r],r)!==!1;);},i=function(t){var i={};for(var a in r(t,function(t){var r=t.attributes[e];n(i,r)&&(i[r]=t)}),i)return i},a=function(e,t,n){return e.node.insertBefore(t.node,n)},o=function(e,t){for(var n in e)t[n]=e[n]},s=function(e){var t=e.trim().replace(/\s+</g,`<`).replace(/>\s+/g,`>`);return new DOMParser().parseFromString(t,`text/html`).body};function c(e,t){var n=e==null?null:typeof Symbol<`u`&&e[Symbol.iterator]||e[`@@iterator`];if(n!=null){var r,i,a,o,s=[],c=!0,l=!1;try{if(a=(n=n.call(e)).next,t===0){if(Object(n)!==n)return;c=!1}else for(;!(c=(r=a.call(n)).done)&&(s.push(r.value),s.length!==t);c=!0);}catch(e){l=!0,i=e}finally{try{if(!c&&n.return!=null&&(o=n.return(),Object(o)!==o))return}finally{if(l)throw i}}return s}}function l(e,t){return u(e)||c(e,t)||d(e,t)||ee()}function u(e){if(Array.isArray(e))return e}function d(e,t){if(e){if(typeof e==`string`)return f(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n===`Object`&&e.constructor&&(n=e.constructor.name),n===`Map`||n===`Set`)return Array.from(e);if(n===`Arguments`||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return f(e,t)}}function f(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n<t;n++)r[n]=e[n];return r}function ee(){throw TypeError(`Invalid attempt to destructure non-iterable instance.
+In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var p={STRING:`string`,INT:`number`,BOOL:`boolean`},m={},h=function(e,t,n){return{attrName:e,propName:t,type:n}};r([[`style`,`cssText`],[`class`,`className`]],function(e){var t=l(e,2),n=t[0],r=t[1];m[n]=h(n,r||n,p.STRING)}),r([`autofocus`,`draggable`,`hidden`,`checked`,`multiple`,`muted`,`selected`],function(e){m[e]=h(e,e,p.BOOL)}),r([[`tabindex`,`tabIndex`]],function(e){var t=l(e,2),n=t[0],r=t[1];m[n]=h(n,r,p.INT)});var g={xlink:{prefix:`xlink:`,resource:`http://www.w3.org/1999/xlink`},xml:{prefix:`xml:`,resource:`http://www.w3.org/XML/1998/namespace`}},_=function(e,t,n,r){switch(t){case p.STRING:n===m.style.propName?r===null?e.style[n]=``:e.style[n]=r:r===null?e[n]=``:e[n]=r;break;case p.INT:if(r===null){var i=n.toLowerCase();e.removeAttribute(i)}else if(r===`0`)e[n]=0;else if(r===`-1`)e[n]=-1;else{var a=parseInt(r,10);isNaN(a)||(e[n]=a)}break;case p.BOOL:[``,`true`].indexOf(r)<0?e[n]=!1:e[n]=!0;break}},te=function(e,n){r(n,function(n){if(t(m,n)){var r=m[n];_(e.node,r.type,r.propName,null)}else n in e.node&&_(e.node,p.STRING,n,null),e.node.removeAttribute(n);delete e.attributes[n]})},ne=function(e,n){for(var r in n){var i=n[r];if(e.attributes[r]=i,t(m,r)){var a=m[r];_(e.node,a.type,a.propName,i);continue}if(r.startsWith(g.xlink.prefix)){e.node.setAttributeNS(g.xlink.resource,r,i);continue}if(r.startsWith(g.xml.prefix)){e.node.setAttributeNS(g.xml.resource,r,i);continue}r in e.node&&_(e.node,p.STRING,r,i),e.node.setAttribute(r,i||``)}},re=function(e,t){for(var n in m){var r=m[n].propName,i=e.getAttribute(n);n===m.style.attrName?t[n]=e.style[r]:typeof i==`string`&&(t[n]=i)}},ie=function(e){return Array.prototype.reduce.call(e.attributes,function(e,n){return t(m,n.name)||(e[n.name]=n.value),e},{})},ae=function(e){var t=ie(e);return re(e,t),t},oe=function(e,t){var n=[],r={};for(var i in t.attributes){var a=t.attributes[i],o=e.attributes[i];a!==o&&o===void 0&&n.push(i)}for(var s in e.attributes){var c=t.attributes[s],l=e.attributes[s];c!==l&&l!==void 0&&(r[s]=l)}te(t,n),ne(t,r)},se=`data-key`;function ce(e,t,n){var o=e.children.length,s=t.children.length;if(!(!o&&!s)){var c=i(t.children),l=Array(o);c===void 0?r(e.children,function(e,r){var i=t.children[r];i===void 0?(t.node.appendChild(e.node),l[r]=e):(n(e,i),l[r]=i)}):r(e.children,function(e,r){var i=t.node.childNodes,o=e.attributes[se];if(Object.prototype.hasOwnProperty.call(c,o)){var s=c[o];Array.prototype.indexOf.call(i,s.node)!==r&&a(t,s,i[r]),l[r]=s,delete c[o],n(e,l[r])}else a(t,e,i[r]),l[r]=e}),t.children=l;var u=t.node.childNodes.length,d=u-o;if(d>0)for(;d>0;)t.node.removeChild(t.node.childNodes[u-1]),u--,d--}}var le=function e(t,n,r){if(!(!t||!n)){r||=n.node.parentNode;var i=t.content&&t.content!==n.content;if(t.type!==n.type||i)return r.replaceChild(t.node,n.node),o(t,n);oe(t,n),ce(t,n,e)}},v=function e(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1;typeof t==`string`&&(t=s(t));var i=t.tagName===`BODY`,a=t.childNodes,o=a?a.length:0;if(i){if(o>1)throw Error(`[OmDomDom]: Your element should not have more than one root node.`);if(o===0)throw Error(`[OmDomDom]: Your element should have at least one root node.`);return e(a[0])}var c=t.nodeType===3?`text`:t.nodeType===8?`comment`:t.tagName.toLowerCase(),l=n||c===`svg`,u=t.nodeType===1?ae(t):{},d=o>0?null:t.textContent,f=Array(o);return r(a,function(t,n){f[n]=e(t,l)}),{type:c,attributes:u,children:f,content:d,node:t,isSVGContext:l}};function ue(e,t){var n=[];for(var r of t){let t=e(r);if(t)n.push(t);else break}return n}function de(e,t){let n=0;for(;n<t.length&&e(t[n]);)n++;return t.slice(n)}function fe(e){return e.map(e=>e.key+`: `+e.value).join(`
+`)}function y(e){return{cookies:e.filter(e=>e.key==`Cookie`).map(e=>e.value),error:x(`Error`,e),query:x(`Query`,e),pageTitle:x(`PageTitle`,e),events:S(`Event`,e).map(w),actions:S(`Trigger`,e).map(T)}}function b(e){return y(C(e.trim().split(`
+`)))}function x(e,t){return t.find(t=>t.key==e)?.value}function S(e,t){return t.filter(t=>t.key==e).map(e=>e.value)}function C(e){return ue(pe,e)}function pe(e){let t=e.match(/^(\w+): (.*)$/);if(t)return{key:t[1],value:t[2]}}function w(e){let[t,n]=E(e);return{name:t,detail:JSON.parse(n)}}function T(e){let[t,n]=E(e);return[t,n]}function E(e){let t=e.indexOf(`|`);if(t===-1){let t=Error(`Bad Encoding, Expected Segment`);throw t.message=e,t}return[e.slice(0,t),e.slice(t+1)]}function me(e,t,n,r,i){let a=[{key:`Cookie`,value:decodeURI(document.cookie)},{key:`Query`,value:window.location.search}],o,s;return i instanceof FormData?o=i:i&&(s=i),{viewId:e,action:t,state:n,requestId:r,meta:a,form:o,value:s}}function he(e){let t=[`|ACTION|`,`ViewId: `+e.viewId,`Action: `+e.action];return e.state&&t.push(`State: `+e.state),t.push(`RequestId: `+e.requestId),[t.join(`
+`),fe(e.meta)].join(`
+`)+ge(e.value)}function ge(e){return e?`
 
-omdomdom/lib/omdomdom.es.js:
-  (*!
-    * @license MIT (https://github.com/geotrev/omdomdom/blob/master/LICENSE)
-    * Omdomdom v0.3.2 (https://github.com/geotrev/omdomdom)
-    * Copyright 2023 George Treviranus <geowtrev@gmail.com>
-    *)
-*/
+`+e:``}var _e=0;function ve(){return{requestId:++_e,isCancelled:!1}}function ye(e){return new DOMParser().parseFromString(e,`text/html`)}function be(e){let t=e.querySelector(`style`);return{content:e.querySelector(`div`),css:t}}function xe(e,t){let n=D(t,e,`ViewId`),r=D(t,e,`Action`);return{kind:`response`,requestId:parseInt(D(t,e,`RequestId`),0),targetViewId:void 0,viewId:n,action:r,meta:y(e),body:t.join(`
+`)}}function D(e,t,n){let r=x(n,t);if(!r)throw new O(`Missing Required Metadata: `+n,e.join(`
+`));return r}var O=class extends Error{constructor(e,t){super(e+`
+`+t),this.name=`ProtocolError`}},k=`${window.location.protocol===`https:`?`wss:`:`ws:`}//${window.location.host}${window.location.pathname}`,A=class{socket;hasEverConnected=!1;isConnected=!1;reconnectDelay=0;queue=[];events;constructor(e=k){this.events=new EventTarget;let t=new WebSocket(e);this.socket=t}connect(e=k,t=!1){let n=t?new WebSocket(e):this.socket;this.socket=n;function r(e){console.error(`Connect Error`,e)}function i(e){console.error(`Socket Error`,e)}n.addEventListener(`error`,r),n.addEventListener(`open`,e=>{console.log(`Websocket Connected`),this.hasEverConnected&&document.dispatchEvent(new Event(`hyp-socket-reconnect`)),this.isConnected=!0,this.hasEverConnected=!0,this.reconnectDelay=1e3,n.removeEventListener(`error`,r),n.addEventListener(`error`,i),document.dispatchEvent(new Event(`hyp-socket-connect`)),this.runQueue()}),n.addEventListener(`close`,t=>{console.log(`CLOSE SOCKET`),this.isConnected&&document.dispatchEvent(new Event(`hyp-socket-disconnect`)),this.isConnected=!1,n.removeEventListener(`error`,i),this.hasEverConnected&&(console.log(`Reconnecting in `+this.reconnectDelay/1e3+`s`),setTimeout(()=>this.connect(e,!0),this.reconnectDelay)),n.removeEventListener(`error`,i)}),n.addEventListener(`message`,e=>this.onMessage(e))}async sendAction(e){if(this.isConnected){let t=he(e);this.socket.send(t)}else this.queue.push(e)}runQueue(){let e=this.queue.pop();e&&(console.log(`runQueue: `,e),this.sendAction(e),this.runQueue())}onMessage(e){let{command:t,metas:n,rest:r}=j(e.data),i=parseInt(a(`RequestId`),0);function a(t){let r=x(t,n);if(!r)throw new O(`Missing Required Metadata: `+t,e.data);return r}function o(e){return{requestId:i,targetViewId:void 0,viewId:a(`ViewId`),action:a(`Action`),meta:y(n),body:e.join(`
+`)}}function s(e){let t=o(e);return t.targetViewId=x(`TargetViewId`,n),t}function c(e){let t=e[0];return{requestId:i,meta:y(n),url:t}}function l(e){let{requestId:t,meta:n,viewId:r,action:i}=o(e),[s,c]=T(a(`Trigger`));return{requestId:t,meta:n,viewId:r,action:i,targetViewId:s,targetAction:c}}function u(e){let{requestId:t,meta:n,viewId:r,action:i}=o(e);return{requestId:t,meta:n,viewId:r,action:i,event:w(a(`Event`))}}switch(t){case`|UPDATE|`:this.dispatchEvent(new CustomEvent(`update`,{detail:s(r)}));return;case`|RESPONSE|`:this.dispatchEvent(new CustomEvent(`response`,{detail:o(r)}));return;case`|REDIRECT|`:return this.dispatchEvent(new CustomEvent(`redirect`,{detail:c(r)}));case`|TRIGGER|`:return this.dispatchEvent(new CustomEvent(`trigger`,{detail:l(r)}));case`|EVENT|`:return this.dispatchEvent(new CustomEvent(`event`,{detail:u(r)}));default:throw new O(`Unknown Server Command: `+t,e.data)}}addEventListener(e,t){this.events.addEventListener(e,t)}dispatchEvent(e){this.events.dispatchEvent(e)}disconnect(){this.isConnected=!1,this.hasEverConnected=!1,this.socket.close()}};function j(e){let t=e.split(`
+`),n=t[0],r=C(t.slice(1));return{command:n,metas:r,rest:de(e=>e==``,t.slice(r.length+1))}}function M(e,t=100,n={}){if(typeof e!=`function`)throw TypeError(`Expected the first parameter to be a function, got \`${typeof e}\`.`);if(t<0)throw RangeError("`wait` must not be negative.");if(typeof n==`boolean`)throw TypeError("The `options` parameter must be an object, not a boolean. Use `{immediate: true}` instead.");let{immediate:r}=n,i,a,o,s,c;function l(){let t=i,n=a;return i=void 0,a=void 0,c=e.apply(t,n),c}function u(){let e=Date.now()-s;e<t&&e>=0?o=setTimeout(u,t-e):(o=void 0,r||(c=l()))}let d=function(...e){if(i&&this!==i&&Object.getPrototypeOf(this)===Object.getPrototypeOf(i))throw Error(`Debounced method called with different contexts of the same prototype.`);i=this,a=e,s=Date.now();let n=r&&!o;if(o||=setTimeout(u,t),n)return c=l(),c};return Object.defineProperty(d,`isPending`,{get(){return o!==void 0}}),d.clear=()=>{o&&(clearTimeout(o),o=void 0,i=void 0,a=void 0)},d.flush=()=>{o&&d.trigger()},d.trigger=()=>{c=l(),d.clear()},d}var N=e=>e?.runAction!==void 0;function P(e){let t=new Event(`hyp-content`,{bubbles:!0});e.dispatchEvent(t)}function F(e,t){e.querySelectorAll(`[id]`).forEach(n=>{n.runAction=function(e,r){return t(n,e,r)},n.concurrency=n.dataset.concurrency||`Drop`,n.cancelActiveRequest=function(){n.activeRequest&&!n.activeRequest?.isCancelled&&(n.activeRequest.isCancelled=!0)},P(e)})}function Se(e){I(`keydown`,e)}function Ce(e){I(`keyup`,e)}function I(e,t){document.addEventListener(e,function(n){if(!(n.target instanceof HTMLElement)){console.warn(`listenKeyEvent received event with non HTMLElment as EventTarget: %o`,n);return}let r=n.target,i=`on`+e+n.key,a=r.dataset[i];if(!a)return;n.preventDefault();let o=V(r);if(!o){console.error(`Missing target: `,r);return}t(o,a)})}function L(e,t){document.addEventListener(e,function(n){if(!(n.target instanceof Element)){console.warn(`listenBubblingEvent received an event with non Elment as EventTarget: %o`,n);return}let r=n.target.closest(`[data-on`+e+`]`);if(!r)return;n.preventDefault();let i=V(r);if(!i){console.error(`Missing target: `,r);return}let a=r.dataset[`on`+e];if(a===void 0){console.error(`Missing action: `,r,e);return}t(i,a)})}function we(e){L(`click`,e)}function Te(e){L(`dblclick`,e)}function Ee(e){document.addEventListener(`hyp-load`,function(t){let n=t.detail.onLoad,r=t.detail.target;e(r,n)}),document.addEventListener(`hyp-mouseenter`,function(t){let n=t.detail.onMouseEnter,r=t.detail.target;e(r,n)}),document.addEventListener(`hyp-mouseleave`,function(t){let n=t.detail.onMouseLeave,r=t.detail.target;e(r,n)})}function R(e){e.querySelectorAll(`[data-onload]`).forEach(e=>{let t=parseInt(e.dataset.delay||``)||0,n=e.dataset.onload;setTimeout(()=>{let t=V(e);if(e.dataset.onload!=n)return;let r=new CustomEvent(`hyp-load`,{bubbles:!0,detail:{target:t,onLoad:n}});e.dispatchEvent(r)},t)})}function z(e){e.querySelectorAll(`[data-onmouseenter]`).forEach(e=>{let t=e.dataset.onmouseenter,n=H(e);e.onmouseenter=()=>{let r=new CustomEvent(`hyp-mouseenter`,{bubbles:!0,detail:{target:n,onMouseEnter:t}});e.dispatchEvent(r)}})}function B(e){e.querySelectorAll(`[data-onmouseleave]`).forEach(e=>{let t=e.dataset.onmouseleave,n=H(e);e.onmouseleave=()=>{let r=new CustomEvent(`hyp-mouseleave`,{bubbles:!0,detail:{target:n,onMouseLeave:t}});e.dispatchEvent(r)}})}function De(e){document.addEventListener(`change`,function(t){if(!(t.target instanceof HTMLElement)){console.warn(`listenChange received an event with non HTMLElment as EventTarget: %o`,t);return}let n=t.target.closest(`[data-onchange]`);if(!n)return;if(t.preventDefault(),n.value===null){console.error(`Missing input value:`,n);return}let r=V(n);if(!r){console.error(`Missing target: listenChange`);return}if(!n.dataset.onchange){console.error(`Missing onchange: `,n);return}e(r,n.dataset.onchange,n.value)})}function Oe(e,t){document.addEventListener(`input`,function(n){if(!(n.target instanceof HTMLElement)){console.warn(`listenInput received an event with non HTMLElment as EventTarget: %o`,n);return}let r=n.target.closest(`[data-oninput]`);if(!r)return;let i=parseInt(r.dataset.delay||``)||250;i<250&&console.warn(`Input delay < 250 can result in poor performance.`),n.preventDefault();let a=V(r);if(!a){console.error(`Missing target: `,r);return}e(a),r.debouncedCallback||=M(()=>{if(!r.dataset.oninput){console.error(`Missing onInput: `,r);return}t(a,r.dataset.oninput,r.value)},i),r.debouncedCallback()})}function ke(e){document.addEventListener(`submit`,function(t){if(!(t.target instanceof HTMLFormElement)){console.warn(`listenFormSubmit received an event with non HTMLElment as EventTarget: %o`,t);return}let n=t.target;if(!n.dataset.onsubmit){console.error(`Missing onSubmit: `,n);return}t.preventDefault();let r=V(n),i=new FormData(n);if(!r){console.error(`Missing target: `,r);return}e(r,n.dataset.onsubmit,i)})}function Ae(e){return e.closest(`[data-target]`)?.dataset.target||e.closest(`[id]`)?.id}function V(e){let t=H(e);if(!N(t)){console.error(`Non HyperView target: `,t);return}return t}function H(e){let t=Ae(e),n=t&&document.getElementById(t);if(!n){console.error(`Cannot find target: `,t,e);return}return n}function je(e){if(e!=Me()){e!=``&&(e=`?`+e);let t=location.pathname+e;window.history.replaceState({},``,t)}}function Me(){let e=window.location.search;return e.startsWith(`?`)?e.substring(1):e}async function Ne(e){let t=window.location.href,n={Accept:`text/html`,"Hyp-ViewId":e.viewId,"Hyp-RequestId":e.requestId,"Hyp-Action":e.action,"Hyp-State":e.state||`()`};console.log(`sendActionHttp`,e.form);let{metas:r,rest:i}=Pe(await(await fetch(t,{method:`POST`,headers:n,body:e.form,redirect:`manual`})).text());return xe(r,i)}function Pe(e){let t=e.split(`
+`);return{metas:C(t.slice(1)),rest:t.slice(2)}}console.log(`Hyperbole 0.7.0b`);var U,W=new Set;async function G(e,t,n){if(e.activeRequest&&!e.activeRequest?.isCancelled&&e.concurrency==`Drop`){console.warn(`Drop action overlapping with active request`,e.activeRequest,t);return}e._timeout=window.setTimeout(()=>{e.classList.add(`hyp-loading`)},100);let r=e.dataset.state,i=ve(),a=me(e.id,t,r,i.requestId,n);if(e.activeRequest=i,!a.form)$.sendAction(a);else{let e=await Ne(a);switch(console.log(`RESPONSE`,e),e.kind){case`redirect`:console.log(`TODO HANDLE REDIRECT`);break;case`response`:q(e);break}}}function K(e){Q(e.targetViewId,e.targetAction)}function Fe(e){let t=document.getElementById(e.viewId);Z(e.event,t)}function Ie(e){console.log(`REDIRECT`,e),Y(e.meta.cookies??[]),window.location.href=e.url}function q(e){let t=J(e.viewId,e);t&&(delete t.activeRequest,clearTimeout(t._timeout),t.classList.remove(`hyp-loading`))}function Le(e){e.meta.error?J(e.viewId,e):e.targetViewId?J(e.targetViewId,e):console.error(`Missing Update targetViewId`,e)}function J(e,t){let n=document.getElementById(e);if(!N(n)){console.error(`Missing Update HyperView Target: `,e,t);return}if(n.activeRequest?.requestId&&t.requestId<n.activeRequest.requestId)return console.warn(`Ignore Stale Action (`+t.requestId+`) vs (`+n.activeRequest.requestId+`): `+t.action),n;if(n.activeRequest?.isCancelled)return console.warn(`Cancelled request`,n.activeRequest?.requestId),delete n.activeRequest,n;let r=be(ye(t.body));if(!r.content)return console.error(`Empty Response!`,t.body),n;ze(r.css);let i=v(n),a=v(r.content),o=a.attributes;if(!t.meta.error&&o.id!=n.id){console.error(`Mismatched ViewId in update - `,o.id,` target:`,n.id);return}let s=o[`data-state`];a.attributes=i.attributes,le(a,i);let c=document.getElementById(n.id);return c?(P(c),s===void 0||s==`()`?delete c.dataset.state:c.dataset.state=s,X(t.meta,c),Y(t.meta.cookies??[]),R(c),z(c),B(c),Re(c),F(c,G),n):(console.warn(`Target Missing: `,n.id),n)}function Y(e){e.forEach(e=>{console.log(`SetCookie: `,e),document.cookie=e})}function X(e,t){e.query!=null&&je(e.query),e.pageTitle!=null&&(document.title=e.pageTitle),e.events?.forEach(e=>{Z(e,t)}),e.actions?.forEach(([e,t])=>{Q(e,t)})}function Z(e,t){setTimeout(()=>{let n=new CustomEvent(e.name,{bubbles:!0,detail:e.detail});(t||document).dispatchEvent(n)},10)}function Q(e,t){setTimeout(()=>{let n=window.Hyperbole?.hyperView(e);n&&G(n,t,void 0)},10)}function Re(e){let t=e.querySelector(`[autofocus]`);t?.focus&&t.focus(),e.querySelectorAll(`input[value]`).forEach(e=>{let t=e.getAttribute(`value`);t!==null&&(e.value=t)}),e.querySelectorAll(`input[type=checkbox]`).forEach(e=>{e.checked=e.dataset.checked==`True`})}function ze(e){if(!e)return;let t=e.sheet?.cssRules;if(t)for(let e=0;e<t.length;e++){let n=t.item(e);n&&W.has(n.cssText)==0&&U.sheet&&(U.sheet.insertRule(n.cssText),W.add(n.cssText))}}function Be(){X(b(document.getElementById(`hyp.metadata`)?.innerText??``),null);let e=document.body.querySelector(`style`);e===null?(console.warn(`rootStyles missing from page, creating...`),U=document.createElement(`style`),U.type=`text/css`,document.body.appendChild(U)):U=e,Ee(async function(e,t){G(e,t,void 0)}),R(document.body),z(document.body),B(document.body),F(document.body,G),we(async function(e,t){G(e,t,void 0)}),Te(async function(e,t){G(e,t,void 0)}),Se(async function(e,t){G(e,t,void 0)}),Ce(async function(e,t){G(e,t)}),ke(async function(e,t,n){G(e,t,n)}),De(async function(e,t,n){G(e,t,n)});function t(e){e.concurrency==`Replace`&&e.cancelActiveRequest()}Oe(t,async function(e,t,n){G(e,t,n)})}document.addEventListener(`DOMContentLoaded`,Be);var $=new A;$.connect(),$.addEventListener(`update`,e=>Le(e.detail)),$.addEventListener(`response`,e=>q(e.detail)),$.addEventListener(`redirect`,e=>Ie(e.detail)),$.addEventListener(`trigger`,e=>K(e.detail)),$.addEventListener(`event`,e=>Fe(e.detail)),window.Hyperbole={runAction:G,parseMetadata:b,action:function(e,...t){return t.reduce((e,t)=>e+` `+JSON.stringify(t),e)},hyperView:function(e){let t=document.getElementById(e);if(!N(t)){console.error(`Element id=`+e+` was not a HyperView`);return}return t},socket:$}})();
 //# sourceMappingURL=hyperbole.js.map
diff --git a/client/dist/hyperbole.js.map b/client/dist/hyperbole.js.map
--- a/client/dist/hyperbole.js.map
+++ b/client/dist/hyperbole.js.map
@@ -1,7 +1,1 @@
-{
-  "version": 3,
-  "sources": ["../node_modules/omdomdom/src/utilities/index.js", "../node_modules/omdomdom/src/parsers/index.js", "../node_modules/omdomdom/src/attributes/records.js", "../node_modules/omdomdom/src/attributes/set-property.js", "../node_modules/omdomdom/src/attributes/index.js", "../node_modules/omdomdom/src/children/index.js", "../node_modules/omdomdom/src/omdomdom.js", "../src/lib.ts", "../src/message.ts", "../src/action.ts", "../src/sockets.ts", "../node_modules/debounce/index.js", "../src/hyperview.ts", "../src/events.ts", "../src/browser.ts", "../src/response.ts", "../package.json", "../src/index.ts"],
-  "sourcesContent": ["const DATA_KEY_ATTRIBUTE = \"data-key\"\n\n/**\n * Checks if the object property exists.\n * @param {Object.<string, any>} obj\n * @param {string} prop\n * @returns {boolean}\n */\nexport const hasProperty = (obj, prop) =>\n  Object.prototype.hasOwnProperty.call(obj, prop)\n\n/**\n * Checks if a property in a keyMap is unique. if not, it's skipped.\n * @param {Object.<string, VirtualNode>} map\n * @param {string} key\n * @returns {boolean}\n */\nconst keyIsValid = (map, key) => {\n  if (!key) return false\n\n  if (hasProperty(map, key)) {\n    // eslint-disable-next-line no-console\n    console.warn(\n      \"[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.\"\n    )\n    return false\n  }\n\n  return true\n}\n\n/**\n * lodash forEach, basically.\n * If `false` is explicitly returned, break the loop.\n * @param {[]} items\n * @param fn\n */\nexport const forEach = (items, fn) => {\n  const length = items.length\n  let idx = -1\n\n  if (!length) return\n\n  while (++idx < length) {\n    if (fn(items[idx], idx) === false) break\n  }\n}\n\n/**\n * Generates a <key: vNode> map of a virtual node list.\n * @param {VirtualNode[]} children\n * @returns {Object.<string, VirtualNode>|undefined}\n */\nexport const createKeyMap = (children) => {\n  const map = {}\n\n  forEach(children, (child) => {\n    const key = child.attributes[DATA_KEY_ATTRIBUTE]\n    if (keyIsValid(map, key)) {\n      map[key] = child\n    }\n  })\n\n  // Cheap way to check if the map contains keys,\n  // and if so, returns the map on the first iteration.\n  for (const _ in map) {\n    return map\n  }\n}\n\nexport const insertBefore = (parent, child, refNode) => {\n  return parent.node.insertBefore(child.node, refNode)\n}\n\n/**\n * Transfers properties from one virtual node to another.\n * @param {VirtualNode} template\n * @param {VirtualNode} vNode\n */\nexport const assignVNode = (template, vNode) => {\n  for (let property in template) {\n    vNode[property] = template[property]\n  }\n}\n", "/**\n * Convert stringified HTML into valid HTML, stripping all extra spaces.\n * @param {string} htmlString\n */\nexport const toHTML = (htmlString) => {\n  /**\n   * Remove all extraneous whitespace:\n   * - From the beginning + end of the document fragment\n   * - If there's more than one space before a left tag bracket, replace them with one\n   * - If there's more than one space before a right tag bracket, replace them with one\n   */\n  const processedDOMString = htmlString\n    .trim()\n    .replace(/\\s+</g, \"<\")\n    .replace(/>\\s+/g, \">\")\n\n  const parser = new DOMParser()\n  const context = parser.parseFromString(processedDOMString, \"text/html\")\n\n  return context.body\n}\n", "import { forEach } from \"../utilities\"\n\nconst Types = { STRING: \"string\", INT: \"number\", BOOL: \"boolean\" }\nconst DomProperties = {}\n\n/**\n * Creates a new record for DomProperties\n * @param {string} attrName\n * @param {string} propName\n * @param {string} type\n */\nconst createRecord = (attrName, propName, type) => ({\n  attrName,\n  propName,\n  type,\n})\n\n// Set string records\nconst stringProps = [\n  // Style is a special snowflake, and needs an extra property to set its text\n  // value. This is less confusing and achieves the same result with one reflow,\n  // compared to a new reflow for each Element.style[styleProperty] setter.\n  [\"style\", \"cssText\"],\n  [\"class\", \"className\"],\n]\nforEach(stringProps, ([attr, property]) => {\n  DomProperties[attr] = createRecord(attr, property || attr, Types.STRING)\n})\n\n// Set boolean records\nconst booleanProps = [\n  \"autofocus\",\n  \"draggable\",\n  \"hidden\",\n  \"checked\",\n  \"multiple\",\n  \"muted\",\n  \"selected\",\n]\nforEach(booleanProps, (attr) => {\n  DomProperties[attr] = createRecord(attr, attr, Types.BOOL)\n})\n\n// Set Integer records\nconst integerProps = [[\"tabindex\", \"tabIndex\"]]\nforEach(integerProps, ([attr, property]) => {\n  DomProperties[attr] = createRecord(attr, property, Types.INT)\n})\n\n/**\n * Attributes with these namespaces require `setAttributeNS`\n * @type {Object.<string, string>}\n */\nconst Namespace = {\n  xlink: {\n    prefix: \"xlink:\",\n    resource: \"http://www.w3.org/1999/xlink\",\n  },\n  xml: {\n    prefix: \"xml:\",\n    resource: \"http://www.w3.org/XML/1998/namespace\",\n  },\n}\n\nexport { Types, Namespace, DomProperties }\n", "import { Types, DomProperties } from \"./records\"\n\n/**\n * Set a given attribute as a property, if it has a property equivalent\n * @param {HTMLElement} node\n * @param {string|number|boolean} type\n * @param {string} prop\n * @param {*} value\n */\nexport const setProperty = (element, type, prop, value) => {\n  switch (type) {\n    case Types.STRING: {\n      if (prop === DomProperties.style.propName) {\n        if (value === null) {\n          element.style[prop] = \"\"\n        } else {\n          element.style[prop] = value\n        }\n      } else if (value === null) {\n        element[prop] = \"\"\n      } else {\n        element[prop] = value\n      }\n      break\n    }\n\n    case Types.INT: {\n      if (value === null) {\n        const attr = prop.toLowerCase()\n        // The attribute needs to be removed to reset the property,\n        // otherwise the value coerced + reflected onto the attribute.\n        element.removeAttribute(attr)\n      } else if (value === \"0\") {\n        element[prop] = 0\n      } else if (value === \"-1\") {\n        element[prop] = -1\n      } else {\n        const parsed = parseInt(value, 10)\n        if (!isNaN(parsed)) {\n          element[prop] = parsed\n        }\n      }\n      break\n    }\n\n    case Types.BOOL: {\n      if ([\"\", \"true\"].indexOf(value) < 0) {\n        element[prop] = false\n      } else {\n        element[prop] = true\n      }\n      break\n    }\n\n    default:\n      break\n  }\n}\n", "import { hasProperty, forEach } from \"../utilities\"\nimport { Namespace, DomProperties, Types } from \"./records\"\nimport { setProperty } from \"./set-property\"\n\n/**\n * Removes stale attributes from the element.\n * @param {HTMLElement} vNode\n * @param {string[]} attributes\n */\nconst removeAttributes = (vNode, attributes) => {\n  forEach(attributes, (attrName) => {\n    if (hasProperty(DomProperties, attrName)) {\n      const propertyRecord = DomProperties[attrName]\n      setProperty(\n        vNode.node,\n        propertyRecord.type,\n        propertyRecord.propName,\n        null\n      )\n    } else {\n      if (attrName in vNode.node) {\n        setProperty(vNode.node, Types.STRING, attrName, null)\n      }\n      vNode.node.removeAttribute(attrName)\n    }\n\n    delete vNode.attributes[attrName]\n  })\n}\n\n/**\n * Adds attributes to the element.\n * @param {VirtualNode} vNode\n * @param {Object.<string, string>} attributes\n */\nconst setAttributes = (vNode, attributes) => {\n  for (let attrName in attributes) {\n    const value = attributes[attrName]\n    vNode.attributes[attrName] = value\n\n    if (hasProperty(DomProperties, attrName)) {\n      const propertyRecord = DomProperties[attrName]\n      setProperty(\n        vNode.node,\n        propertyRecord.type,\n        propertyRecord.propName,\n        value\n      )\n      continue\n    }\n\n    // Set namespaced properties using setAttributeNS\n    if (attrName.startsWith(Namespace.xlink.prefix)) {\n      vNode.node.setAttributeNS(Namespace.xlink.resource, attrName, value)\n      continue\n    }\n\n    if (attrName.startsWith(Namespace.xml.prefix)) {\n      vNode.node.setAttributeNS(Namespace.xml.resource, attrName, value)\n      continue\n    }\n\n    if (attrName in vNode.node) {\n      setProperty(vNode.node, Types.STRING, attrName, value)\n    }\n    vNode.node.setAttribute(attrName, value || \"\")\n  }\n}\n\n/**\n * Checks property-based attributes. If the attribute exists,\n * then we should set its property value.\n * @param {HTMLElement} element\n * @param {Object.<string, string>} attributes\n */\nconst getPropertyValues = (element, attributes) => {\n  for (let attrName in DomProperties) {\n    const propertyRecord = DomProperties[attrName]\n    const propName = propertyRecord.propName\n    const attrValue = element.getAttribute(attrName)\n\n    if (attrName === DomProperties.style.attrName) {\n      attributes[attrName] = element.style[propName]\n    } else if (typeof attrValue === \"string\") {\n      attributes[attrName] = attrValue\n    }\n  }\n}\n\n/**\n * Gets non-property attributes.\n * @param {HTMLElement} element\n * @returns {Object.<string, string>}\n */\nconst getBaseAttributes = (element) => {\n  return Array.prototype.reduce.call(\n    element.attributes,\n    (attributes, attrName) => {\n      if (!hasProperty(DomProperties, attrName.name)) {\n        attributes[attrName.name] = attrName.value\n      }\n      return attributes\n    },\n    {}\n  )\n}\n\n/**\n * Gets all attributes.\n * @param {HTMLElement} element\n * @returns {Object.<string, string>}\n */\nexport const getAttributes = (element) => {\n  const attributes = getBaseAttributes(element)\n  getPropertyValues(element, attributes)\n\n  return attributes\n}\n\n/**\n * Reconcile attributes from vNode to template\n * @param {VirtualNode} template\n * @param {VirtualNode} vNode\n */\nexport const updateAttributes = (template, vNode) => {\n  let removedAttributes = []\n  let changedAttributes = {}\n\n  // Get stale attributes\n  for (let attrName in vNode.attributes) {\n    const oldValue = vNode.attributes[attrName]\n    const nextValue = template.attributes[attrName]\n    if (oldValue === nextValue) continue\n\n    if (typeof nextValue === \"undefined\") {\n      removedAttributes.push(attrName)\n    }\n  }\n\n  // Get changed or new attributes\n  for (let attrName in template.attributes) {\n    const oldValue = vNode.attributes[attrName]\n    const nextValue = template.attributes[attrName]\n    if (oldValue === nextValue) continue\n\n    if (typeof nextValue !== \"undefined\") {\n      changedAttributes[attrName] = nextValue\n    }\n  }\n\n  // Add and remove attributes\n  removeAttributes(vNode, removedAttributes)\n  setAttributes(vNode, changedAttributes)\n}\n", "import { forEach, createKeyMap, insertBefore } from \"../utilities\"\n\nconst DATA_KEY_ATTRIBUTE = \"data-key\"\n\n/**\n * Both template and vNode have arrays of virtual nodes. Diff them.\n * @param {VirtualNode} template - new virtual node tree.\n * @param {VirtualNode} vNode - existing virtual node tree.\n */\nexport function patchChildren(template, vNode, patch) {\n  const templateChildrenLength = template.children.length\n  const vNodeChildrenLength = vNode.children.length\n\n  if (!templateChildrenLength && !vNodeChildrenLength) return\n\n  const vNodeKeyMap = createKeyMap(vNode.children)\n  let nextChildren = Array(templateChildrenLength)\n\n  if (vNodeKeyMap !== undefined) {\n    forEach(template.children, (templateChild, idx) => {\n      const childNodes = vNode.node.childNodes\n      const key = templateChild.attributes[DATA_KEY_ATTRIBUTE]\n\n      if (Object.prototype.hasOwnProperty.call(vNodeKeyMap, key)) {\n        const keyedChild = vNodeKeyMap[key]\n\n        if (Array.prototype.indexOf.call(childNodes, keyedChild.node) !== idx) {\n          insertBefore(vNode, keyedChild, childNodes[idx])\n        }\n\n        nextChildren[idx] = keyedChild\n\n        // Remove entry to prevent dupes\n        delete vNodeKeyMap[key]\n        patch(templateChild, nextChildren[idx])\n      } else {\n        insertBefore(vNode, templateChild, childNodes[idx])\n        nextChildren[idx] = templateChild\n      }\n    })\n  } else {\n    forEach(template.children, (templateChild, idx) => {\n      const vNodeChild = vNode.children[idx]\n\n      if (typeof vNodeChild !== \"undefined\") {\n        patch(templateChild, vNodeChild)\n        nextChildren[idx] = vNodeChild\n      } else {\n        vNode.node.appendChild(templateChild.node)\n        nextChildren[idx] = templateChild\n      }\n    })\n  }\n\n  vNode.children = nextChildren\n\n  // Remove any real nodes that are left over from the diff\n  let childNodesLength = vNode.node.childNodes.length\n  let delta = childNodesLength - templateChildrenLength\n\n  if (delta > 0) {\n    while (delta > 0) {\n      vNode.node.removeChild(vNode.node.childNodes[childNodesLength - 1])\n      childNodesLength--\n      delta--\n    }\n  }\n}\n", "import { forEach, assignVNode } from \"./utilities\"\nimport { toHTML } from \"./parsers\"\nimport { updateAttributes, getAttributes } from \"./attributes\"\nimport { patchChildren } from \"./children\"\n\n/**\n * Object representation of a DOM element.\n * @typedef VirtualNode\n * @type {Object}\n * @property {string} type - The type of node. E.g., tag name, \"comment\", or \"text\".\n * @property {Object.<string, string>} attributes - List of attributes, if any.\n * @property {TextNode|CommentNode} content - Text or comment content, if any.\n * @property {VirtualNode[]} children - Child virtual nodes, if any.\n * @property {HTMLElement} node - The corresponding DOM element.\n * @property {boolean} isSVGContext\n */\n\n/**\n * Reconcile differences between two virtual DOM trees.\n * @param {VirtualNode} template - new virtual node tree.\n * @param {VirtualNode} vNode - existing virtual node tree.\n * @param {Node} rootNode - the HTML element containing the current node context\n */\nexport const patch = (template, vNode, rootNode) => {\n  // This came happen if a null result is given for either\n  // template or vNode. Impossible to compare - return.\n  if (!template || !vNode) return\n\n  rootNode = rootNode || vNode.node.parentNode\n  const contentChanged = template.content && template.content !== vNode.content\n\n  // If the type or content changed, replace the node completely\n  if (template.type !== vNode.type || contentChanged) {\n    rootNode.replaceChild(template.node, vNode.node)\n    return assignVNode(template, vNode)\n  }\n\n  // Update attributes, if any\n  updateAttributes(template, vNode)\n\n  // Diff child nodes recursively\n  patchChildren(template, vNode, patch)\n}\n\n/**\n * Renders a node into the given root context. This happens one time,\n * when a component is first rendered.\n * All subsequent renders are the result of reconciliation.\n * @param {VirtualNode} vDOM\n * @param {ShadowRoot|HTMLElement} root\n */\nexport const render = (vNode, root) => {\n  root.appendChild(vNode.node)\n}\n\n/**\n * Creates a new virtual DOM from a root node.\n * @param {HTMLElement|ShadowRoot|HTMLBodyElement} node\n * @param {boolean} isSVGContext\n * @returns {VirtualNode}\n */\nexport const create = (node, isSVGContext = false) => {\n  if (typeof node === \"string\") {\n    node = toHTML(node)\n  }\n\n  const isRoot = node.tagName === \"BODY\"\n  const childNodes = node.childNodes\n  const numChildNodes = childNodes ? childNodes.length : 0\n\n  // toHTML returns a `body` tag as its root node, but we want the first child only\n  if (isRoot) {\n    if (numChildNodes > 1) {\n      throw new Error(\n        \"[OmDomDom]: Your element should not have more than one root node.\"\n      )\n    } else if (numChildNodes === 0) {\n      throw new Error(\n        \"[OmDomDom]: Your element should have at least one root node.\"\n      )\n    } else {\n      return create(childNodes[0])\n    }\n  }\n\n  const type =\n    node.nodeType === 3\n      ? \"text\"\n      : node.nodeType === 8\n      ? \"comment\"\n      : node.tagName.toLowerCase()\n  const isSVG = isSVGContext || type === \"svg\"\n  const attributes = node.nodeType === 1 ? getAttributes(node) : {}\n  const content = numChildNodes > 0 ? null : node.textContent\n\n  // Recursively build children\n  const children = Array(numChildNodes)\n  forEach(childNodes, (child, idx) => {\n    children[idx] = create(child, isSVG)\n  })\n\n  return { type, attributes, children, content, node, isSVGContext: isSVG }\n}\n", "\n\nexport function takeWhileMap<T, A>(pred: (val: T) => A | undefined, lines: T[]): A[] {\n  var output = []\n  for (var line of lines) {\n    let a = pred(line)\n    if (a)\n      output.push(a)\n    else\n      break;\n  }\n\n  return output\n}\n\nexport function dropWhile<T, A>(pred: (val: T) => A | undefined, lines: T[]): T[] {\n  let index = 0;\n  while (index < lines.length && pred(lines[index])) {\n    index++;\n  }\n  return lines.slice(index);\n}\n\n", "\nimport { takeWhileMap, dropWhile } from \"./lib\"\n\n\n\nexport type Meta = { key: string, value: string }\nexport type ViewId = string\nexport type RequestId = number\nexport type EncodedAction = string\nexport type ViewState = string\n\nexport type RemoteEvent = { name: string, detail: unknown }\n\n\nexport function renderMetas(meta: Meta[]): string {\n  return meta.map(m => m.key + \": \" + m.value).join('\\n')\n}\n\nexport type Metadata = {\n  cookies?: string[]\n  // redirect?: string\n  error?: string\n  query?: string\n  events?: RemoteEvent[]\n  actions?: [ViewId, EncodedAction][],\n  pageTitle?: string\n}\n\n\nexport function toMetadata(meta: Meta[]): Metadata {\n\n  return {\n    cookies: meta.filter(m => m.key == \"Cookie\").map(m => m.value),\n    // redirect: metaValue(\"Redirect\", meta),\n    error: metaValue(\"Error\", meta),\n    query: metaValue(\"Query\", meta),\n    pageTitle: metaValue(\"PageTitle\", meta),\n    events: metaValuesAll(\"Event\", meta).map(parseRemoteEvent),\n    actions: metaValuesAll(\"Trigger\", meta).map(parseAction),\n  }\n}\n\n// viewId: meta.find(m => m.key == \"VIEW-ID\")?.value,\n\nexport function parseMetadata(input: string): Metadata {\n  let metas = takeWhileMap(parseMeta, input.trim().split(\"\\n\"))\n  return toMetadata(metas)\n}\n\n\nexport function metaValue(key: string, metas: Meta[]): string | undefined {\n  return metas.find(m => m.key == key)?.value\n}\n\nexport function metaValuesAll(key: string, metas: Meta[]): string[] {\n  return metas.filter(m => m.key == key).map(m => m.value)\n}\n\nexport type SplitMessage = {\n  command: string,\n  metas: Meta[],\n  rest: string[]\n}\n\n\nexport function splitMessage(message: string): SplitMessage {\n  let lines = message.split(\"\\n\")\n  let command: string = lines[0]\n  let metas: Meta[] = takeWhileMap(parseMeta, lines.slice(1))\n  // console.log(\"Split Metadata\", lines.length)\n  // console.log(\" [0]\", lines[0])\n  // console.log(\" [1]\", lines[1])\n  let rest = dropWhile(l => l == \"\", lines.slice(metas.length + 1))\n\n  return { command, metas, rest }\n}\n\nexport function parseMeta(line: string): Meta | undefined {\n  let match = line.match(/^(\\w+)\\: (.*)$/)\n  if (match) {\n    return {\n      key: match[1],\n      value: match[2]\n    }\n  }\n}\n\n\nexport function parseRemoteEvent(input: string): RemoteEvent {\n  let [name, data] = breakNextSegment(input)\n  return {\n    name,\n    detail: JSON.parse(data)\n  }\n}\n\nexport function parseAction(input: string): [ViewId, string] {\n  let [viewId, action] = breakNextSegment(input)\n  return [viewId, action]\n}\n\nfunction breakNextSegment(input: string): [string, string] {\n  let ix = input.indexOf('|')\n  if (ix === -1) {\n    let err = new Error(\"Bad Encoding, Expected Segment\")\n    err.message = input\n    throw err\n  }\n  return [input.slice(0, ix), input.slice(ix + 1)]\n}\n\n", "\nimport { Meta, ViewId, RequestId, EncodedAction, ViewState } from \"./message\"\nimport * as message from \"./message\"\n\n\n\nexport type ActionMessage = {\n  viewId: ViewId\n  action: EncodedAction\n  requestId: RequestId\n  state?: ViewState\n  meta: Meta[]\n  form: URLSearchParams | undefined\n}\n\n\n\n\nexport function actionMessage(id: ViewId, action: EncodedAction, state: ViewState | undefined, reqId: RequestId, form?: FormData): ActionMessage {\n  let meta: Meta[] = [\n    { key: \"Cookie\", value: decodeURI(document.cookie) },\n    { key: \"Query\", value: window.location.search }\n  ]\n\n  return { viewId: id, action, state, requestId: reqId, meta, form: toSearch(form) }\n}\n\nexport function toSearch(form?: FormData): URLSearchParams | undefined {\n  if (!form) return undefined\n\n  const params = new URLSearchParams()\n\n  form.forEach((value, key) => {\n    params.append(key, value as string)\n  })\n\n  return params\n}\n\nexport function renderActionMessage(msg: ActionMessage): string {\n  let header = [\n    \"|ACTION|\",\n    \"ViewId: \" + msg.viewId,\n    \"Action: \" + msg.action,\n  ]\n\n\n  if (msg.state) {\n    header.push(\"State: \" + msg.state)\n  }\n\n  header.push(\"RequestId: \" + msg.requestId)\n\n  return [\n    header.join('\\n'),\n    message.renderMetas(msg.meta),\n  ].join('\\n') + renderForm(msg.form)\n}\n\n\nexport function renderForm(form: URLSearchParams | undefined): string {\n  if (!form) return \"\"\n  return \"\\n\\n\" + form\n}\n\nlet globalRequestId: RequestId = 0\n\nexport type Request = {\n  requestId: RequestId\n  isCancelled: boolean\n}\n\nexport function newRequest(): Request {\n  let requestId = ++globalRequestId\n  return { requestId, isCancelled: false }\n}\n\n\n\n// Sanitized Encoding ------------------------------------\n\nexport function encodedParam(action: string, param: string): string {\n  return action + ' ' + sanitizeParam(param)\n}\n\nfunction sanitizeParam(param: string): string {\n  if (param == \"\") {\n    return \"|\"\n  }\n\n  return param.replace(/_/g, \"\\\\_\").replace(/\\s+/g, \"_\")\n}\n", "import { ActionMessage, renderActionMessage } from './action'\nimport { ResponseBody } from \"./response\"\nimport * as message from \"./message\"\nimport { ViewId, RequestId, EncodedAction, metaValue, Metadata, RemoteEvent } from \"./message\"\n\nconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst defaultAddress = `${protocol}//${window.location.host}${window.location.pathname}`\n\ninterface SocketConnectionEventMap {\n  \"update\": CustomEvent<Update>;\n  \"response\": CustomEvent<Update>;\n  \"redirect\": CustomEvent<Redirect>;\n  \"trigger\": CustomEvent<Trigger>;\n  \"event\": CustomEvent<JSEvent>;\n}\n\nexport class SocketConnection {\n  socket: WebSocket\n\n  hasEverConnected: boolean = false\n  isConnected: boolean = false\n  reconnectDelay: number = 0\n  queue: ActionMessage[] = []\n  events: EventTarget\n\n  constructor(addr = defaultAddress) {\n    this.events = new EventTarget()\n    const sock = new WebSocket(addr)\n    this.socket = sock\n  }\n\n  connect(addr = defaultAddress, createSocket = false) {\n    const sock = createSocket ? new WebSocket(addr) : this.socket\n    this.socket = sock\n\n    function onConnectError(ev: Event) {\n      console.error(\"Connect Error\", ev)\n    }\n\n    function onSocketError(ev: Event) {\n      console.error(\"Socket Error\", ev)\n    }\n\n\n    // initial connection errors\n    sock.addEventListener('error', onConnectError)\n\n    sock.addEventListener('open', (_event) => {\n      console.log(\"Websocket Connected\")\n\n      if (this.hasEverConnected) {\n        document.dispatchEvent(new Event(\"hyp-socket-reconnect\"))\n      }\n\n      this.isConnected = true\n      this.hasEverConnected = true\n      this.reconnectDelay = 1000\n      sock.removeEventListener('error', onConnectError)\n      sock.addEventListener('error', onSocketError)\n\n      document.dispatchEvent(new Event(\"hyp-socket-connect\"))\n\n      this.runQueue()\n    })\n\n    sock.addEventListener('close', _ => {\n      console.log(\"CLOSE SOCKET\")\n      if (this.isConnected) {\n        document.dispatchEvent(new Event(\"hyp-socket-disconnect\"))\n      }\n\n      this.isConnected = false\n      sock.removeEventListener('error', onSocketError)\n\n      // attempt to reconnect in 1s\n      if (this.hasEverConnected) {\n        console.log(\"Reconnecting in \" + (this.reconnectDelay / 1000) + \"s\")\n        setTimeout(() => this.connect(addr, true), this.reconnectDelay)\n      }\n\n      sock.removeEventListener('error', onSocketError)\n    })\n\n    sock.addEventListener('message', ev => this.onMessage(ev))\n  }\n\n  async sendAction(action: ActionMessage) {\n    if (this.isConnected) {\n      let msg = renderActionMessage(action)\n      this.socket.send(msg)\n    }\n    else {\n      this.queue.push(action)\n    }\n  }\n\n  private runQueue() {\n    // send all messages queued while disconnected \n    let next: ActionMessage | undefined = this.queue.pop()\n    if (next) {\n      console.log(\"runQueue: \", next)\n      this.sendAction(next)\n      this.runQueue()\n    }\n  }\n\n\n  // full responses will never be sent over!\n  private onMessage(event: MessageEvent) {\n    let { command, metas, rest } = message.splitMessage(event.data)\n    // console.log(\"MESSAGE\", command, metas, rest)\n\n    let requestId = parseInt(requireMeta(\"RequestId\"), 0)\n\n    function requireMeta(key: string): string {\n      let val = metaValue(key, metas)\n      if (!val) throw new ProtocolError(\"Missing Required Metadata: \" + key, event.data)\n      return val\n    }\n\n    function parseResponse(rest: string[]): Update {\n      let viewId = requireMeta(\"ViewId\")\n      let action = requireMeta(\"Action\")\n      return {\n        requestId,\n        targetViewId: undefined,\n        viewId,\n        action,\n        meta: message.toMetadata(metas),\n        body: rest.join(\"\\n\"),\n      }\n    }\n\n    function parseUpdate(rest: string[]): Update {\n      let up = parseResponse(rest)\n      // add the TargetViewId\n      up.targetViewId = metaValue(\"TargetViewId\", metas)\n      return up\n    }\n\n    function parseRedirect(rest: string[]): Redirect {\n      let url = rest[0]\n      return {\n        requestId,\n        meta: message.toMetadata(metas),\n        url\n      }\n    }\n\n    function parseTrigger(rest: string[]): Trigger {\n      let { requestId, meta, viewId, action } = parseResponse(rest)\n      let [targetViewId, targetAction] = message.parseAction(requireMeta(\"Trigger\"))\n      return { requestId, meta, viewId, action, targetViewId, targetAction }\n    }\n\n    function parseEvent(rest: string[]): JSEvent {\n      let { requestId, meta, viewId, action } = parseResponse(rest)\n      let event = message.parseRemoteEvent(requireMeta(\"Event\"))\n      return { requestId, meta, viewId, action, event }\n    }\n\n\n    switch (command) {\n\n      case \"|UPDATE|\":\n        return this.dispatchEvent(new CustomEvent(\"update\", { detail: parseUpdate(rest) }))\n\n      case \"|RESPONSE|\":\n        return this.dispatchEvent(new CustomEvent(\"response\", { detail: parseResponse(rest) }))\n\n      case \"|REDIRECT|\":\n        return this.dispatchEvent(new CustomEvent(\"redirect\", { detail: parseRedirect(rest) }))\n\n      case \"|TRIGGER|\":\n        return this.dispatchEvent(new CustomEvent(\"trigger\", { detail: parseTrigger(rest) }))\n\n      case \"|EVENT|\":\n        return this.dispatchEvent(new CustomEvent(\"event\", { detail: parseEvent(rest) }))\n\n      default:\n        throw new ProtocolError(\"Unknown Server Command: \" + command, event.data)\n    }\n  }\n\n\n  // so what if they send remote events in the page? trigger, redirect, page title, etc...\n  // we aren't connected yet on a page thing\n\n  // private async waitMessage(reqId: RequestId, id: ViewId): Promise<ParsedResponse> {\n  //   return new Promise((resolve, reject) => {\n  //     const onMessage = (event: MessageEvent) => {\n  //       let data: string = event.data\n  //       let lines = data.split(\"\\n\").slice(1)  // drop the command line\n  //\n  //       let parsed = splitMetadata(lines)\n  //       let metadata: Metadata = parsed.metadata\n  //\n  //       if (!metadata.requestId) {\n  //         console.error(\"Missing RequestId!\", metadata, event.data)\n  //         return\n  //       }\n  //\n  //       if (metadata.requestId != reqId) {\n  //         // skip, it's not us!\n  //         return\n  //       }\n  //\n  //\n  //       // We have found our message. Remove the listener\n  //       this.socket.removeEventListener('message', onMessage)\n  //\n  //       // set the cookies. These happen automatically in http\n  //       metadata.cookies.forEach((cookie: string) => {\n  //         document.cookie = cookie\n  //       })\n  //\n  //       if (metadata.error) {\n  //         reject(new FetchError(id, metadata.error, parsed.rest.join('\\n')))\n  //         return\n  //       }\n  //\n  //       resolve(parsed)\n  //     }\n  //\n  //     this.socket.addEventListener('message', onMessage)\n  //     this.socket.addEventListener('error', reject)\n  //   })\n  // }\n\n  addEventListener<K extends keyof SocketConnectionEventMap>(e: K, cb: (ev: SocketConnectionEventMap[K]) => void) {\n    this.events.addEventListener(e,\n      // @ts-ignore: HACK\n      cb\n    )\n  }\n\n  dispatchEvent<K extends keyof SocketConnectionEventMap>(e: SocketConnectionEventMap[K]) {\n    this.events.dispatchEvent(e)\n  }\n\n  disconnect() {\n    this.isConnected = false\n    this.hasEverConnected = false\n    this.socket.close()\n  }\n}\n\n\nexport type Update = {\n  requestId: RequestId\n  meta: Metadata\n  viewId: ViewId\n  targetViewId?: ViewId\n  action: EncodedAction\n  body: ResponseBody\n}\n\nexport type Redirect = {\n  requestId: RequestId\n  meta: Metadata\n  url: string\n}\n\nexport type Trigger = {\n  requestId: RequestId\n  meta: Metadata\n  viewId: ViewId\n  action: EncodedAction\n  targetViewId: ViewId\n  targetAction: string\n}\n\nexport type JSEvent = {\n  requestId: RequestId\n  meta: Metadata\n  viewId: ViewId\n  action: EncodedAction\n  event: RemoteEvent\n}\n\nexport type MessageType = string\n\n\n// PARSING MESSAGE  ---------------------------------------\n\nexport class ProtocolError extends Error {\n  constructor(description: string, body: string) {\n    super(description + \"\\n\" + body)\n    this.name = \"ProtocolError\"\n  }\n}\n", "export default function debounce(function_, wait = 100, options = {}) {\n\tif (typeof function_ !== 'function') {\n\t\tthrow new TypeError(`Expected the first parameter to be a function, got \\`${typeof function_}\\`.`);\n\t}\n\n\tif (wait < 0) {\n\t\tthrow new RangeError('`wait` must not be negative.');\n\t}\n\n\tif (typeof options === 'boolean') {\n\t\tthrow new TypeError('The `options` parameter must be an object, not a boolean. Use `{immediate: true}` instead.');\n\t}\n\n\tconst {immediate} = options;\n\n\tlet storedContext;\n\tlet storedArguments;\n\tlet timeoutId;\n\tlet timestamp;\n\tlet result;\n\n\tfunction run() {\n\t\tconst callContext = storedContext;\n\t\tconst callArguments = storedArguments;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t\tresult = function_.apply(callContext, callArguments);\n\t\treturn result;\n\t}\n\n\tfunction later() {\n\t\tconst last = Date.now() - timestamp;\n\n\t\tif (last < wait && last >= 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t} else {\n\t\t\ttimeoutId = undefined;\n\n\t\t\tif (!immediate) {\n\t\t\t\tresult = run();\n\t\t\t}\n\t\t}\n\t}\n\n\tconst debounced = function (...arguments_) {\n\t\tif (\n\t\t\tstoredContext\n\t\t\t&& this !== storedContext\n\t\t\t&& Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)\n\t\t) {\n\t\t\tthrow new Error('Debounced method called with different contexts of the same prototype.');\n\t\t}\n\n\t\tstoredContext = this; // eslint-disable-line unicorn/no-this-assignment\n\t\tstoredArguments = arguments_;\n\t\ttimestamp = Date.now();\n\n\t\tconst callNow = immediate && !timeoutId;\n\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\n\t\tif (callNow) {\n\t\t\tresult = run();\n\t\t\treturn result;\n\t\t}\n\n\t\treturn undefined;\n\t};\n\n\tObject.defineProperty(debounced, 'isPending', {\n\t\tget() {\n\t\t\treturn timeoutId !== undefined;\n\t\t},\n\t});\n\n\tdebounced.clear = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(timeoutId);\n\t\ttimeoutId = undefined;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t};\n\n\tdebounced.flush = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tdebounced.trigger();\n\t};\n\n\tdebounced.trigger = () => {\n\t\tresult = run();\n\n\t\tdebounced.clear();\n\t};\n\n\treturn debounced;\n}\n", "import { type Request } from \"./action\";\n\nexport interface HyperView extends HTMLElement {\n  runAction(action: string): Promise<void>;\n  activeRequest?: Request;\n  cancelActiveRequest(): void;\n  concurrency: ConcurrencyMode;\n  _timeout?: number;\n}\n\nexport const isHyperView = (ele: any): ele is HyperView => {\n  return ele?.runAction !== undefined;\n};\n\nexport type ConcurrencyMode = string;\n\nexport function dispatchContent(node: HTMLElement): void {\n  let event = new Event(\"hyp-content\", { bubbles: true })\n  node.dispatchEvent(event)\n}\n\nexport function enrichHyperViews(node: HTMLElement, runAction: (target: HyperView, action: string, form?: FormData) => Promise<void>): void {\n  // enrich all the hyperviews\n  node.querySelectorAll<HyperView>(\"[id]\").forEach((element) => {\n    element.runAction = function(action: string) {\n      return runAction(element, action)\n    }\n\n    element.concurrency = element.dataset.concurrency || \"Drop\"\n\n    element.cancelActiveRequest = function() {\n      if (element.activeRequest && !element.activeRequest?.isCancelled) {\n        element.activeRequest.isCancelled = true\n      }\n    }\n\n    dispatchContent(node)\n  })\n}\n", "\nimport debounce from 'debounce'\nimport { encodedParam } from './action'\nimport { HyperView, isHyperView } from './hyperview'\n\nexport type UrlFragment = string\n\nexport function listenKeydown(cb: (target: HyperView, action: string) => void): void {\n  listenKeyEvent(\"keydown\", cb)\n}\n\nexport function listenKeyup(cb: (target: HyperView, action: string) => void): void {\n  listenKeyEvent(\"keyup\", cb)\n}\n\nexport function listenKeyEvent(event: \"keyup\" | \"keydown\", cb: (target: HyperView, action: string) => void): void {\n\n  document.addEventListener(event, function(e: KeyboardEvent) {\n    if (!(e.target instanceof HTMLElement)) {\n      console.warn(\"listenKeyEvent received event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let source = e.target\n\n    let datasetKey = \"on\" + event + e.key\n    let action = source.dataset[datasetKey]\n    if (!action) return\n\n    e.preventDefault()\n    const target =  nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: \", source)\n      return\n    }\n    cb(target, action)\n  })\n}\n\nexport function listenBubblingEvent(event: string, cb: (_target: HyperView, action: string) => void): void {\n  document.addEventListener(event, function(e) {\n    if (!(e.target instanceof HTMLElement)) {\n      console.warn(\"listenBubblingEvent received an event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let el = e.target\n\n    // clicks can fire on internal elements. Find the parent with a click handler\n    let source = el.closest<HTMLElement>(\"[data-on\" + event + \"]\")\n    if (!source) return\n\n    e.preventDefault()\n    let target = nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: \", source)\n      return\n    }\n    const action = source.dataset[\"on\" + event]\n    if (action === undefined) {\n      console.error(\"Missing action: \", source, event)\n      return\n    }\n    cb(target, action)\n  })\n}\n\nexport function listenClick(cb: (target: HyperView, action: string) => void): void {\n  listenBubblingEvent(\"click\", cb)\n}\n\nexport function listenDblClick(cb: (target: HyperView, action: string) => void): void {\n  listenBubblingEvent(\"dblclick\", cb)\n}\n\n\nexport function listenTopLevel(cb: (target: HyperView, action: string) => void): void {\n  document.addEventListener(\"hyp-load\", function(e: CustomEvent) {\n    let action = e.detail.onLoad\n    let target = e.detail.target\n    cb(target, action)\n  })\n\n  document.addEventListener(\"hyp-mouseenter\", function(e: CustomEvent) {\n    let action = e.detail.onMouseEnter\n    let target = e.detail.target\n    cb(target, action)\n  })\n\n  document.addEventListener(\"hyp-mouseleave\", function(e: CustomEvent) {\n    let action = e.detail.onMouseLeave\n    let target = e.detail.target\n    cb(target, action)\n  })\n}\n\n\nexport function listenLoad(node: HTMLElement): void {\n\n  // it doesn't really matter WHO runs this except that it should have target\n  node.querySelectorAll<HTMLElement>(\"[data-onload]\").forEach((load) => {\n    let delay = parseInt(load.dataset.delay || \"\") || 0\n    let onLoad = load.dataset.onload\n    // console.log(\"load start\", load.dataset.onLoad)\n\n    // load no longer exists!\n    // we should clear the timeout or back out if the dom is replaced in the interem\n    setTimeout(() => {\n      let target = nearestHyperViewTarget(load)\n      // console.log(\"load go\", load.dataset.onLoad)\n\n      if (load.dataset.onload != onLoad) {\n        // the onLoad no longer exists\n        return\n      }\n\n      const event = new CustomEvent(\"hyp-load\", { bubbles: true, detail: { target, onLoad } })\n      load.dispatchEvent(event)\n    }, delay)\n  })\n}\n\nexport function listenMouseEnter(node: HTMLElement): void {\n  node.querySelectorAll<HTMLElement>(\"[data-onmouseenter]\").forEach((node) => {\n    let onMouseEnter = node.dataset.onmouseenter\n\n    let target = nearestAnyTarget(node)\n\n    node.onmouseenter = () => {\n      const event = new CustomEvent(\"hyp-mouseenter\", { bubbles: true, detail: { target, onMouseEnter } })\n      node.dispatchEvent(event)\n    }\n  })\n}\n\nexport function listenMouseLeave(node: HTMLElement): void {\n  node.querySelectorAll<HTMLElement>(\"[data-onmouseleave]\").forEach((node) => {\n    let onMouseLeave = node.dataset.onmouseleave\n\n    let target = nearestAnyTarget(node)\n\n    node.onmouseleave = () => {\n      const event = new CustomEvent(\"hyp-mouseleave\", { bubbles: true, detail: { target, onMouseLeave } })\n      node.dispatchEvent(event)\n    }\n  })\n}\n\n\nexport function listenChange(cb: (target: HyperView, action: string) => void): void {\n  document.addEventListener(\"change\", function(e) {\n    if (!(e.target instanceof HTMLElement)) {\n      console.warn(\"listenChange received an event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let el = e.target\n\n    let source = el.closest<HTMLInputElement>(\"[data-onchange]\")\n\n    if (!source) return\n    e.preventDefault()\n\n    if (source.value === null) {\n      console.error(\"Missing input value:\", source)\n      return\n    }\n\n    let target = nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: listenChange\")\n      return\n    }\n    if (!source.dataset.onchange) {\n      console.error(\"Missing onchange: \", source)\n      return\n    }\n    let action = encodedParam(source.dataset.onchange, source.value)\n    cb(target, action)\n  })\n}\n\ninterface LiveInputElement extends HTMLInputElement {\n  debouncedCallback?: Function;\n}\n\nexport function listenInput(startedTyping: (target: HyperView) => void, cb: (target: HyperView, action: string) => void): void {\n  document.addEventListener(\"input\", function(e) {\n    if (!(e.target instanceof HTMLElement)) {\n      console.warn(\"listenInput received an event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let el = e.target\n    const source = el.closest<LiveInputElement>(\"[data-oninput]\")\n\n    if (!source) return\n\n    let delay = parseInt(source.dataset.delay || \"\") || 250\n    if (delay < 250) {\n      console.warn(\"Input delay < 250 can result in poor performance.\")\n    }\n\n    e.preventDefault()\n\n    const target = nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: \", source)\n      return\n    }\n\n    // I want to CANCEL the active request as soon as we start typing\n    startedTyping(target)\n\n    if (!source.debouncedCallback) {\n      source.debouncedCallback = debounce(() => {\n        if (!source.dataset.oninput) {\n          console.error(\"Missing onInput: \", source)\n          return\n        }\n        const action = encodedParam(source.dataset.oninput, source.value)\n        cb(target, action)\n      }, delay)\n    }\n\n    source.debouncedCallback()\n  })\n}\n\n\n\nexport function listenFormSubmit(cb: (target: HyperView, action: string, form: FormData) => void): void {\n  document.addEventListener(\"submit\", function(e) {\n    if (!(e.target instanceof HTMLFormElement)) {\n      console.warn(\"listenFormSubmit received an event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let form = e.target\n\n\n    if (!form.dataset.onsubmit) {\n      console.error(\"Missing onSubmit: \", form)\n      return\n    }\n\n    e.preventDefault()\n\n    let target = nearestHyperViewTarget(form)\n    const formData = new FormData(form)\n    if (!target) {\n      console.error(\"Missing target: \", form)\n      return\n    }\n    cb(target, form.dataset.onsubmit, formData)\n  })\n}\n\nfunction nearestTargetId(node: HTMLElement): string | undefined {\n  let targetData = node.closest<HTMLElement>(\"[data-target]\")\n  return targetData?.dataset.target || node.closest(\"[id]\")?.id\n}\n\nfunction nearestHyperViewTarget(node: HTMLElement): HyperView | undefined {\n  const target = nearestAnyTarget(node)\n\n  if (!isHyperView(target)) {\n    console.error(\"Non HyperView target: \", target)\n    return\n  }\n\n  return target\n}\n\nfunction nearestAnyTarget(node: HTMLElement): HTMLElement | undefined {\n  let targetId = nearestTargetId(node)\n  let target = targetId && document.getElementById(targetId)\n\n  if (!target) {\n    console.error(\"Cannot find target: \", targetId, node)\n    return\n  }\n\n  return target\n}\n", "\nexport function setQuery(query: string) {\n  if (query != currentQuery()) {\n    if (query != \"\") query = \"?\" + query\n    let url = location.pathname + query\n    // console.log(\"history.replaceState(\", url, \")\")\n    window.history.replaceState({}, \"\", url)\n  }\n}\n\nfunction currentQuery(): string {\n  const query = window.location.search;\n  return query.startsWith('?') ? query.substring(1) : query;\n}\n", "\nimport { ViewId, Metadata } from './message'\n\n\n\nexport type Response = {\n  meta: Metadata\n  body: ResponseBody\n}\n\nexport type ResponseBody = string\n\nexport function parseResponse(res: ResponseBody): LiveUpdate {\n  const parser = new DOMParser()\n  const doc = parser.parseFromString(res, 'text/html')\n  const css = doc.querySelector<HTMLStyleElement>(\"style\")\n  const content = doc.querySelector<HTMLElement>(\"div\")\n\n  return {\n    content: content,\n    css: css\n  }\n}\n\nexport type LiveUpdate = {\n  content: HTMLElement | null\n  css: HTMLStyleElement | null\n}\n\n\nexport class FetchError extends Error {\n  viewId: ViewId\n  body: string\n  constructor(viewId: ViewId, msg: string, body: string) {\n    super(msg)\n    this.viewId = viewId\n    this.name = \"Fetch Error\"\n    this.body = body\n  }\n}\n", "{\n  \"name\": \"web-ui\",\n  \"version\": \"0.6.0\",\n  \"description\": \"Development -----------\",\n  \"main\": \"index.js\",\n  \"directories\": {\n    \"client\": \"client\"\n  },\n  \"scripts\": {\n    \"build\": \"esbuild src/index.ts --bundle --minify --sourcemap --outfile=dist/hyperbole.js\",\n    \"dev\": \"esbuild src/index.ts --bundle --minify --sourcemap --watch=forever --outfile=dist/hyperbole.js\",\n    \"check\": \"tsc --noEmit --preserveWatchOutput --watch\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"esbuild\": \"^0.28.0\",\n    \"typescript\": \"^6.0.3\"\n  },\n  \"dependencies\": {\n    \"omdomdom\": \"^0.3.2\",\n    \"debounce\": \"^3.0.0\"\n  }\n}\n", "import { patch, create } from \"omdomdom/lib/omdomdom.es.js\"\nimport { SocketConnection, Update, Redirect, Trigger, JSEvent } from './sockets'\nimport { listenChange, listenClick, listenDblClick, listenFormSubmit, listenLoad, listenTopLevel, listenInput, listenKeydown, listenKeyup, listenMouseEnter, listenMouseLeave } from './events'\nimport { actionMessage, newRequest } from './action'\nimport { ViewId, Metadata, parseMetadata, RemoteEvent, EncodedAction } from './message'\nimport { setQuery } from \"./browser\"\nimport { parseResponse, LiveUpdate } from './response'\nimport { dispatchContent, enrichHyperViews, HyperView, isHyperView } from \"./hyperview\"\n\nimport PACKAGE from '../package.json';\n\n\n// console.log(\"VERSION 2\", INIT_PAGE, INIT_STATE)\nconsole.log(\"Hyperbole \" + PACKAGE.version + \"b\")\n\n\nlet rootStyles: HTMLStyleElement;\nlet addedRulesIndex = new Set();\n\n\n\n\n\n// Run an action in a given HyperView\nasync function runAction(target: HyperView, action: string, form?: FormData) {\n\n  if (target.activeRequest && !target.activeRequest?.isCancelled) {\n    // Active Request!\n    if (target.concurrency == \"Drop\") {\n      console.warn(\"Drop action overlapping with active request (\" + target.activeRequest + \")\", action)\n      return\n    }\n  }\n\n  target._timeout = window.setTimeout(() => {\n    // add loading after 100ms, not right away\n    // if it runs shorter than that we probably don't want to show the user any loading feedback\n    target.classList.add(\"hyp-loading\")\n  }, 100)\n\n  let state = target.dataset.state\n\n  let req = newRequest()\n  let msg = actionMessage(target.id, action, state, req.requestId, form)\n\n  // Set the requestId\n  target.activeRequest = req\n\n  sock.sendAction(msg)\n}\n\nfunction handleTrigger(trigger: Trigger) {\n  runTrigger(trigger.targetViewId, trigger.targetAction)\n}\n\nfunction handleEvent(ev: JSEvent) {\n  let target = document.getElementById(ev.viewId)\n  runRemoteEvent(ev.event, target)\n}\n\n// TODO: redirect concurrency\nfunction handleRedirect(red: Redirect) {\n  console.log(\"REDIRECT\", red)\n\n  // the other metdata doesn't apply, they are all specific to the page\n  applyCookies(red.meta.cookies ?? [])\n\n  window.location.href = red.url\n}\n\n// in-process update\nfunction handleResponse(res: Update) {\n  // console.log(\"Handle Response\", res)\n  let target = handleUpdate(res)\n  if (!target) return\n\n  // clean up the request\n  delete target.activeRequest\n  clearTimeout(target._timeout)\n  target.classList.remove(\"hyp-loading\")\n}\n\nfunction handleUpdate(res: Update): HyperView | undefined {\n  // console.log(\"|UPDATE|\", res)\n\n  let targetViewId = res.targetViewId || res.viewId\n  let target = document.getElementById(targetViewId)\n\n  if (!isHyperView(target)) {\n    console.error(\"Missing Update HyperView Target: \", targetViewId, res)\n    return\n  }\n\n  if (target.activeRequest?.requestId && res.requestId < target.activeRequest.requestId) {\n    // this should only happen on Replace, since other requests should be dropped\n    // but it's safe to assume we never want to apply an old requestId\n    console.warn(\"Ignore Stale Action (\" + res.requestId + \") vs (\" + target.activeRequest.requestId + \"): \" + res.action)\n    return target\n  }\n  else if (target.activeRequest?.isCancelled) {\n    console.warn(\"Cancelled request\", target.activeRequest?.requestId)\n    delete target.activeRequest\n    return target\n  }\n\n  let update: LiveUpdate = parseResponse(res.body)\n\n  if (!update.content) {\n    console.error(\"Empty Response!\", res.body)\n    return target\n  }\n\n  // First, update the stylesheet\n  addCSS(update.css)\n\n\n  // Patch the node\n  const old: VNode = create(target)\n  let next: VNode = create(update.content)\n  let atts = next.attributes\n\n  if (!res.meta.error && atts[\"id\"] != target.id) {\n    console.error(\"Mismatched ViewId in update - \", atts[\"id\"], \" target:\", target.id)\n    return\n  }\n\n  let state = atts[\"data-state\"]\n  next.attributes = old.attributes\n\n\n  patch(next, old)\n\n\n  // Emit relevant events\n  let newTarget = document.getElementById(target.id)\n\n  if (!newTarget) {\n    console.warn(\"Target Missing: \", target.id)\n    return target\n  }\n\n  dispatchContent(newTarget)\n\n  // re-add state attribute \n  if (state === undefined || state == \"()\")\n    delete newTarget.dataset.state\n  else\n    newTarget.dataset.state = state\n\n  // execute the metadata, anything that doesn't interrupt the dom update\n  runMetadata(res.meta, newTarget)\n  applyCookies(res.meta.cookies ?? [])\n\n  // now way for these to bubble)\n  listenLoad(newTarget)\n  listenMouseEnter(newTarget)\n  listenMouseLeave(newTarget)\n  fixInputs(newTarget)\n  enrichHyperViews(newTarget, runAction)\n\n  return target\n}\n// catch (err) {\n//   console.error(\"Caught Error in HyperView (\" + target.id + \"):\\n\", err)\n//\n//   // Hyperbole catches handler errors, and the server controls what to display to the user on an error\n//   //  but if you manage to crash your parent server process somehow, the response may be empty\n//   target.innerHTML = err.body || \"<div style='background:red;color:white;padding:10px'>Hyperbole Internal Error</div>\"\n// }\n\nfunction applyCookies(cookies: string[]) {\n  cookies.forEach((cookie: string) => {\n    console.log(\"SetCookie: \", cookie)\n    document.cookie = cookie\n  })\n}\n\nfunction runMetadata(meta: Metadata, target: HTMLElement | null) {\n  if (meta.query != null) {\n    setQuery(meta.query)\n  }\n\n  if (meta.pageTitle != null) {\n    document.title = meta.pageTitle\n  }\n\n  meta.events?.forEach((remoteEvent) => {\n    runRemoteEvent(remoteEvent, target)\n  })\n\n  meta.actions?.forEach(([viewId, action]) => {\n    runTrigger(viewId, action)\n  })\n}\n\nfunction runRemoteEvent(remoteEvent: RemoteEvent, target: HTMLElement | null) {\n  setTimeout(() => {\n    let event = new CustomEvent(remoteEvent.name, { bubbles: true, detail: remoteEvent.detail })\n    let eventTarget = target || document\n    eventTarget.dispatchEvent(event)\n  }, 10)\n}\n\nfunction runTrigger(viewId: ViewId, action: EncodedAction) {\n  setTimeout(() => {\n    let view = window.Hyperbole?.hyperView(viewId)\n    if (view) {\n      runAction(view, action)\n    }\n  }, 10)\n}\n\n\nfunction fixInputs(target: HTMLElement) {\n  let focused = target.querySelector<HTMLInputElement>(\"[autofocus]\")\n  if (focused?.focus) {\n    focused.focus()\n  }\n\n  target.querySelectorAll<HTMLInputElement>(\"input[value]\").forEach((input) => {\n    let val = input.getAttribute(\"value\")\n    if (val !== null) {\n      input.value = val\n    }\n  })\n\n  target.querySelectorAll<HTMLInputElement>(\"input[type=checkbox]\").forEach((checkbox) => {\n    let checked = checkbox.dataset.checked == \"True\"\n    checkbox.checked = checked\n  })\n}\n\nfunction addCSS(src: HTMLStyleElement | null) {\n  if (!src) return;\n  const rules = src.sheet?.cssRules\n  if (!rules) return;\n  for (let i = 0; i < rules.length; i++) {\n    const rule = rules.item(i)\n    if (rule && addedRulesIndex.has(rule.cssText) == false && rootStyles.sheet) {\n      rootStyles.sheet.insertRule(rule.cssText);\n      addedRulesIndex.add(rule.cssText);\n    }\n  }\n}\n\n\n\n\nfunction init() {\n  // metadata attached to initial page loads need to be executed\n  let meta = parseMetadata(document.getElementById(\"hyp.metadata\")?.innerText ?? \"\")\n  // runMetadataImmediate(meta)\n  runMetadata(meta, null)\n\n  const style = document.body.querySelector('style')\n\n  if (style !== null) {\n    rootStyles = style\n  } else {\n    console.warn(\"rootStyles missing from page, creating...\")\n    rootStyles = document.createElement(\"style\")\n    rootStyles.type = \"text/css\"\n    document.body.appendChild(rootStyles)\n  }\n\n  listenTopLevel(async function(target: HyperView, action: string) {\n    runAction(target, action)\n  })\n\n  listenLoad(document.body)\n  listenMouseEnter(document.body)\n  listenMouseLeave(document.body)\n  enrichHyperViews(document.body, runAction)\n\n\n  listenClick(async function(target: HyperView, action: string) {\n    // console.log(\"CLICK\", target.id, action)\n    runAction(target, action)\n  })\n\n  listenDblClick(async function(target: HyperView, action: string) {\n    // console.log(\"DBLCLICK\", target.id, action)\n    runAction(target, action)\n  })\n\n  listenKeydown(async function(target: HyperView, action: string) {\n    // console.log(\"KEYDOWN\", target.id, action)\n    runAction(target, action)\n  })\n\n  listenKeyup(async function(target: HyperView, action: string) {\n    // console.log(\"KEYUP\", target.id, action)\n    runAction(target, action)\n  })\n\n  listenFormSubmit(async function(target: HyperView, action: string, form: FormData) {\n    // console.log(\"FORM\", target.id, action, form)\n    runAction(target, action, form)\n  })\n\n  listenChange(async function(target: HyperView, action: string) {\n    runAction(target, action)\n  })\n\n  function onStartedTyping(target: HyperView) {\n    if (target.concurrency == \"Replace\") {\n      target.cancelActiveRequest()\n    }\n  }\n\n  listenInput(onStartedTyping, async function(target: HyperView, action: string) {\n    runAction(target, action)\n  })\n}\n\n\n\n\n\n\ndocument.addEventListener(\"DOMContentLoaded\", init)\n\n\n\n\nconst sock = new SocketConnection()\n// Should we connect to the socket or not?\nsock.connect()\nsock.addEventListener(\"update\", (ev: CustomEvent<Update>) => { handleUpdate(ev.detail) })\nsock.addEventListener(\"response\", (ev: CustomEvent<Update>) => handleResponse(ev.detail))\nsock.addEventListener(\"redirect\", (ev: CustomEvent<Redirect>) => handleRedirect(ev.detail))\nsock.addEventListener(\"trigger\", (ev: CustomEvent<Trigger>) => handleTrigger(ev.detail))\nsock.addEventListener(\"event\", (ev: CustomEvent<JSEvent>) => handleEvent(ev.detail))\n\n\n\n\n\ntype VNode = {\n  // One of three value types are used:\n  // - The tag name of the element\n  // - \"text\" if text node\n  // - \"comment\" if comment node\n  type: string\n\n  // An object whose key/value pairs are the attribute\n  // name and value, respectively\n  attributes: { [key: string]: string | undefined }\n\n  // Is set to `true` if a node is an `svg`, which tells\n  // Omdomdom to treat it, and its children, as such\n  isSVGContext: boolean\n\n  // The content of a \"text\" or \"comment\" node\n  content: string\n\n  // An array of virtual node children\n  children: Array<VNode>\n\n  // The real DOM node\n  node: Node\n}\n\n\n\n\n\ndeclare global {\n  interface Window {\n    Hyperbole?: HyperboleAPI;\n  }\n  interface DocumentEventMap {\n    \"hyp-load\": CustomEvent;\n    \"hyp-mouseenter\": CustomEvent;\n    \"hyp-mouseleave\": CustomEvent;\n  }\n}\n\nexport interface HyperboleAPI {\n  runAction(target: HTMLElement, action: string, form?: FormData): Promise<void>\n  action(con: string, ...params: any[]): string\n  hyperView(viewId: ViewId): HyperView | undefined\n  parseMetadata(input: string): Metadata\n  socket: SocketConnection\n}\n\nwindow.Hyperbole =\n{\n  runAction: runAction,\n  parseMetadata: parseMetadata,\n  action: function(con, ...params: any[]) {\n    return params.reduce((str, param) => str + \" \" + JSON.stringify(param), con);\n  },\n  hyperView: function(viewId) {\n    let element = document.getElementById(viewId)\n    if (!isHyperView(element)) {\n      console.error(\"Element id=\" + viewId + \" was not a HyperView\")\n      return\n    }\n    return element\n  },\n  socket: sock\n}\n"],
-  "mappings": "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",
-  "names": ["DATA_KEY_ATTRIBUTE", "hasProperty", "obj", "prop", "Object", "prototype", "hasOwnProperty", "call", "keyIsValid", "map", "key", "console", "warn", "forEach", "items", "fn", "length", "idx", "createKeyMap", "children", "child", "attributes", "_", "insertBefore", "parent", "refNode", "node", "assignVNode", "template", "vNode", "property", "toHTML", "htmlString", "processedDOMString", "trim", "replace", "parser", "DOMParser", "context", "parseFromString", "body", "Types", "STRING", "INT", "BOOL", "DomProperties", "createRecord", "attrName", "propName", "type", "stringProps", "_ref", "_ref2", "_slicedToArray", "attr", "booleanProps", "integerProps", "_ref3", "_ref4", "Namespace", "xlink", "prefix", "resource", "xml", "setProperty", "element", "value", "style", "toLowerCase", "removeAttribute", "parsed", "parseInt", "isNaN", "indexOf", "removeAttributes", "propertyRecord", "setAttributes", "startsWith", "setAttributeNS", "setAttribute", "getPropertyValues", "attrValue", "getAttribute", "getBaseAttributes", "Array", "reduce", "name", "getAttributes", "updateAttributes", "removedAttributes", "changedAttributes", "oldValue", "nextValue", "push", "patchChildren", "patch", "templateChildrenLength", "vNodeChildrenLength", "vNodeKeyMap", "nextChildren", "undefined", "templateChild", "childNodes", "keyedChild", "vNodeChild", "appendChild", "childNodesLength", "delta", "removeChild", "rootNode", "parentNode", "contentChanged", "content", "replaceChild", "create", "node", "isSVGContext", "arguments", "length", "undefined", "toHTML", "isRoot", "tagName", "childNodes", "numChildNodes", "Error", "type", "nodeType", "toLowerCase", "isSVG", "attributes", "getAttributes", "content", "textContent", "children", "Array", "forEach", "child", "idx", "takeWhileMap", "pred", "lines", "output", "line", "a", "dropWhile", "index", "renderMetas", "meta", "m", "toMetadata", "metaValue", "metaValuesAll", "parseRemoteEvent", "parseAction", "parseMetadata", "input", "metas", "takeWhileMap", "parseMeta", "key", "splitMessage", "message", "lines", "command", "rest", "dropWhile", "l", "line", "match", "name", "data", "breakNextSegment", "viewId", "action", "ix", "err", "actionMessage", "id", "action", "state", "reqId", "form", "meta", "toSearch", "params", "value", "key", "renderActionMessage", "msg", "header", "renderMetas", "renderForm", "globalRequestId", "newRequest", "encodedParam", "param", "sanitizeParam", "protocol", "defaultAddress", "SocketConnection", "addr", "sock", "createSocket", "onConnectError", "ev", "onSocketError", "_event", "_", "action", "msg", "renderActionMessage", "next", "event", "command", "metas", "rest", "splitMessage", "requestId", "requireMeta", "key", "val", "metaValue", "ProtocolError", "parseResponse", "viewId", "toMetadata", "parseUpdate", "up", "parseRedirect", "url", "parseTrigger", "meta", "targetViewId", "targetAction", "parseAction", "parseEvent", "parseRemoteEvent", "cb", "description", "body", "debounce", "function_", "wait", "options", "immediate", "storedContext", "storedArguments", "timeoutId", "timestamp", "result", "run", "callContext", "callArguments", "later", "last", "debounced", "arguments_", "callNow", "isHyperView", "ele", "dispatchContent", "node", "event", "enrichHyperViews", "runAction", "element", "action", "listenKeydown", "cb", "listenKeyEvent", "listenKeyup", "event", "e", "source", "datasetKey", "action", "target", "nearestHyperViewTarget", "listenBubblingEvent", "listenClick", "listenDblClick", "listenTopLevel", "listenLoad", "node", "load", "delay", "onLoad", "listenMouseEnter", "onMouseEnter", "nearestAnyTarget", "listenMouseLeave", "onMouseLeave", "listenChange", "encodedParam", "listenInput", "startedTyping", "debounce", "listenFormSubmit", "form", "formData", "nearestTargetId", "isHyperView", "targetId", "setQuery", "query", "currentQuery", "url", "parseResponse", "res", "doc", "css", "package_default", "package_default", "rootStyles", "addedRulesIndex", "runAction", "target", "action", "form", "state", "req", "newRequest", "msg", "actionMessage", "sock", "handleTrigger", "trigger", "runTrigger", "handleEvent", "ev", "runRemoteEvent", "handleRedirect", "red", "applyCookies", "handleResponse", "res", "handleUpdate", "targetViewId", "isHyperView", "update", "parseResponse", "addCSS", "old", "create", "next", "atts", "patch", "newTarget", "dispatchContent", "runMetadata", "listenLoad", "listenMouseEnter", "listenMouseLeave", "fixInputs", "enrichHyperViews", "cookies", "cookie", "meta", "setQuery", "remoteEvent", "viewId", "event", "view", "focused", "input", "val", "checkbox", "checked", "src", "rules", "i", "rule", "init", "parseMetadata", "style", "listenTopLevel", "listenClick", "listenDblClick", "listenKeydown", "listenKeyup", "listenFormSubmit", "listenChange", "onStartedTyping", "listenInput", "SocketConnection", "con", "params", "str", "param", "element"]
-}
+{"version":3,"file":"hyperbole.js","names":[],"sources":["../node_modules/omdomdom/lib/omdomdom.es.js","../src/lib.ts","../src/message.ts","../src/action.ts","../src/response.ts","../src/sockets.ts","../node_modules/debounce/index.js","../src/hyperview.ts","../src/events.ts","../src/browser.ts","../src/http.ts","../src/index.ts"],"sourcesContent":["/*!\n  * @license MIT (https://github.com/geotrev/omdomdom/blob/master/LICENSE)\n  * Omdomdom v0.3.2 (https://github.com/geotrev/omdomdom)\n  * Copyright 2023 George Treviranus <geowtrev@gmail.com>\n  */\nvar DATA_KEY_ATTRIBUTE$1 = \"data-key\";\nvar hasProperty = function hasProperty(obj, prop) {\n  return Object.prototype.hasOwnProperty.call(obj, prop);\n};\nvar keyIsValid = function keyIsValid(map, key) {\n  if (!key) return false;\n  if (hasProperty(map, key)) {\n    console.warn(\"[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.\");\n    return false;\n  }\n  return true;\n};\nvar forEach = function forEach(items, fn) {\n  var length = items.length;\n  var idx = -1;\n  if (!length) return;\n  while (++idx < length) {\n    if (fn(items[idx], idx) === false) break;\n  }\n};\nvar createKeyMap = function createKeyMap(children) {\n  var map = {};\n  forEach(children, function (child) {\n    var key = child.attributes[DATA_KEY_ATTRIBUTE$1];\n    if (keyIsValid(map, key)) {\n      map[key] = child;\n    }\n  });\n  for (var _ in map) {\n    return map;\n  }\n};\nvar insertBefore = function insertBefore(parent, child, refNode) {\n  return parent.node.insertBefore(child.node, refNode);\n};\nvar assignVNode = function assignVNode(template, vNode) {\n  for (var property in template) {\n    vNode[property] = template[property];\n  }\n};\n\nvar toHTML = function toHTML(htmlString) {\n  var processedDOMString = htmlString.trim().replace(/\\s+</g, \"<\").replace(/>\\s+/g, \">\");\n  var parser = new DOMParser();\n  var context = parser.parseFromString(processedDOMString, \"text/html\");\n  return context.body;\n};\n\nfunction _iterableToArrayLimit(arr, i) {\n  var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"];\n  if (null != _i) {\n    var _s,\n      _e,\n      _x,\n      _r,\n      _arr = [],\n      _n = !0,\n      _d = !1;\n    try {\n      if (_x = (_i = _i.call(arr)).next, 0 === i) {\n        if (Object(_i) !== _i) return;\n        _n = !1;\n      } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);\n    } catch (err) {\n      _d = !0, _e = err;\n    } finally {\n      try {\n        if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;\n      } finally {\n        if (_d) throw _e;\n      }\n    }\n    return _arr;\n  }\n}\nfunction _slicedToArray(arr, i) {\n  return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n}\nfunction _arrayWithHoles(arr) {\n  if (Array.isArray(arr)) return arr;\n}\nfunction _unsupportedIterableToArray(o, minLen) {\n  if (!o) return;\n  if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n  var n = Object.prototype.toString.call(o).slice(8, -1);\n  if (n === \"Object\" && o.constructor) n = o.constructor.name;\n  if (n === \"Map\" || n === \"Set\") return Array.from(o);\n  if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n}\nfunction _arrayLikeToArray(arr, len) {\n  if (len == null || len > arr.length) len = arr.length;\n  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n  return arr2;\n}\nfunction _nonIterableRest() {\n  throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nvar Types = {\n  STRING: \"string\",\n  INT: \"number\",\n  BOOL: \"boolean\"\n};\nvar DomProperties = {};\nvar createRecord = function createRecord(attrName, propName, type) {\n  return {\n    attrName: attrName,\n    propName: propName,\n    type: type\n  };\n};\nvar stringProps = [[\"style\", \"cssText\"], [\"class\", \"className\"]];\nforEach(stringProps, function (_ref) {\n  var _ref2 = _slicedToArray(_ref, 2),\n    attr = _ref2[0],\n    property = _ref2[1];\n  DomProperties[attr] = createRecord(attr, property || attr, Types.STRING);\n});\nvar booleanProps = [\"autofocus\", \"draggable\", \"hidden\", \"checked\", \"multiple\", \"muted\", \"selected\"];\nforEach(booleanProps, function (attr) {\n  DomProperties[attr] = createRecord(attr, attr, Types.BOOL);\n});\nvar integerProps = [[\"tabindex\", \"tabIndex\"]];\nforEach(integerProps, function (_ref3) {\n  var _ref4 = _slicedToArray(_ref3, 2),\n    attr = _ref4[0],\n    property = _ref4[1];\n  DomProperties[attr] = createRecord(attr, property, Types.INT);\n});\nvar Namespace = {\n  xlink: {\n    prefix: \"xlink:\",\n    resource: \"http://www.w3.org/1999/xlink\"\n  },\n  xml: {\n    prefix: \"xml:\",\n    resource: \"http://www.w3.org/XML/1998/namespace\"\n  }\n};\n\nvar setProperty = function setProperty(element, type, prop, value) {\n  switch (type) {\n    case Types.STRING:\n      {\n        if (prop === DomProperties.style.propName) {\n          if (value === null) {\n            element.style[prop] = \"\";\n          } else {\n            element.style[prop] = value;\n          }\n        } else if (value === null) {\n          element[prop] = \"\";\n        } else {\n          element[prop] = value;\n        }\n        break;\n      }\n    case Types.INT:\n      {\n        if (value === null) {\n          var attr = prop.toLowerCase();\n          element.removeAttribute(attr);\n        } else if (value === \"0\") {\n          element[prop] = 0;\n        } else if (value === \"-1\") {\n          element[prop] = -1;\n        } else {\n          var parsed = parseInt(value, 10);\n          if (!isNaN(parsed)) {\n            element[prop] = parsed;\n          }\n        }\n        break;\n      }\n    case Types.BOOL:\n      {\n        if ([\"\", \"true\"].indexOf(value) < 0) {\n          element[prop] = false;\n        } else {\n          element[prop] = true;\n        }\n        break;\n      }\n  }\n};\n\nvar removeAttributes = function removeAttributes(vNode, attributes) {\n  forEach(attributes, function (attrName) {\n    if (hasProperty(DomProperties, attrName)) {\n      var propertyRecord = DomProperties[attrName];\n      setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, null);\n    } else {\n      if (attrName in vNode.node) {\n        setProperty(vNode.node, Types.STRING, attrName, null);\n      }\n      vNode.node.removeAttribute(attrName);\n    }\n    delete vNode.attributes[attrName];\n  });\n};\nvar setAttributes = function setAttributes(vNode, attributes) {\n  for (var attrName in attributes) {\n    var value = attributes[attrName];\n    vNode.attributes[attrName] = value;\n    if (hasProperty(DomProperties, attrName)) {\n      var propertyRecord = DomProperties[attrName];\n      setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, value);\n      continue;\n    }\n    if (attrName.startsWith(Namespace.xlink.prefix)) {\n      vNode.node.setAttributeNS(Namespace.xlink.resource, attrName, value);\n      continue;\n    }\n    if (attrName.startsWith(Namespace.xml.prefix)) {\n      vNode.node.setAttributeNS(Namespace.xml.resource, attrName, value);\n      continue;\n    }\n    if (attrName in vNode.node) {\n      setProperty(vNode.node, Types.STRING, attrName, value);\n    }\n    vNode.node.setAttribute(attrName, value || \"\");\n  }\n};\nvar getPropertyValues = function getPropertyValues(element, attributes) {\n  for (var attrName in DomProperties) {\n    var propertyRecord = DomProperties[attrName];\n    var propName = propertyRecord.propName;\n    var attrValue = element.getAttribute(attrName);\n    if (attrName === DomProperties.style.attrName) {\n      attributes[attrName] = element.style[propName];\n    } else if (typeof attrValue === \"string\") {\n      attributes[attrName] = attrValue;\n    }\n  }\n};\nvar getBaseAttributes = function getBaseAttributes(element) {\n  return Array.prototype.reduce.call(element.attributes, function (attributes, attrName) {\n    if (!hasProperty(DomProperties, attrName.name)) {\n      attributes[attrName.name] = attrName.value;\n    }\n    return attributes;\n  }, {});\n};\nvar getAttributes = function getAttributes(element) {\n  var attributes = getBaseAttributes(element);\n  getPropertyValues(element, attributes);\n  return attributes;\n};\nvar updateAttributes = function updateAttributes(template, vNode) {\n  var removedAttributes = [];\n  var changedAttributes = {};\n  for (var attrName in vNode.attributes) {\n    var oldValue = vNode.attributes[attrName];\n    var nextValue = template.attributes[attrName];\n    if (oldValue === nextValue) continue;\n    if (typeof nextValue === \"undefined\") {\n      removedAttributes.push(attrName);\n    }\n  }\n  for (var _attrName in template.attributes) {\n    var _oldValue = vNode.attributes[_attrName];\n    var _nextValue = template.attributes[_attrName];\n    if (_oldValue === _nextValue) continue;\n    if (typeof _nextValue !== \"undefined\") {\n      changedAttributes[_attrName] = _nextValue;\n    }\n  }\n  removeAttributes(vNode, removedAttributes);\n  setAttributes(vNode, changedAttributes);\n};\n\nvar DATA_KEY_ATTRIBUTE = \"data-key\";\nfunction patchChildren(template, vNode, patch) {\n  var templateChildrenLength = template.children.length;\n  var vNodeChildrenLength = vNode.children.length;\n  if (!templateChildrenLength && !vNodeChildrenLength) return;\n  var vNodeKeyMap = createKeyMap(vNode.children);\n  var nextChildren = Array(templateChildrenLength);\n  if (vNodeKeyMap !== undefined) {\n    forEach(template.children, function (templateChild, idx) {\n      var childNodes = vNode.node.childNodes;\n      var key = templateChild.attributes[DATA_KEY_ATTRIBUTE];\n      if (Object.prototype.hasOwnProperty.call(vNodeKeyMap, key)) {\n        var keyedChild = vNodeKeyMap[key];\n        if (Array.prototype.indexOf.call(childNodes, keyedChild.node) !== idx) {\n          insertBefore(vNode, keyedChild, childNodes[idx]);\n        }\n        nextChildren[idx] = keyedChild;\n        delete vNodeKeyMap[key];\n        patch(templateChild, nextChildren[idx]);\n      } else {\n        insertBefore(vNode, templateChild, childNodes[idx]);\n        nextChildren[idx] = templateChild;\n      }\n    });\n  } else {\n    forEach(template.children, function (templateChild, idx) {\n      var vNodeChild = vNode.children[idx];\n      if (typeof vNodeChild !== \"undefined\") {\n        patch(templateChild, vNodeChild);\n        nextChildren[idx] = vNodeChild;\n      } else {\n        vNode.node.appendChild(templateChild.node);\n        nextChildren[idx] = templateChild;\n      }\n    });\n  }\n  vNode.children = nextChildren;\n  var childNodesLength = vNode.node.childNodes.length;\n  var delta = childNodesLength - templateChildrenLength;\n  if (delta > 0) {\n    while (delta > 0) {\n      vNode.node.removeChild(vNode.node.childNodes[childNodesLength - 1]);\n      childNodesLength--;\n      delta--;\n    }\n  }\n}\n\nvar patch = function patch(template, vNode, rootNode) {\n  if (!template || !vNode) return;\n  rootNode = rootNode || vNode.node.parentNode;\n  var contentChanged = template.content && template.content !== vNode.content;\n  if (template.type !== vNode.type || contentChanged) {\n    rootNode.replaceChild(template.node, vNode.node);\n    return assignVNode(template, vNode);\n  }\n  updateAttributes(template, vNode);\n  patchChildren(template, vNode, patch);\n};\nvar render = function render(vNode, root) {\n  root.appendChild(vNode.node);\n};\nvar create = function create(node) {\n  var isSVGContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n  if (typeof node === \"string\") {\n    node = toHTML(node);\n  }\n  var isRoot = node.tagName === \"BODY\";\n  var childNodes = node.childNodes;\n  var numChildNodes = childNodes ? childNodes.length : 0;\n  if (isRoot) {\n    if (numChildNodes > 1) {\n      throw new Error(\"[OmDomDom]: Your element should not have more than one root node.\");\n    } else if (numChildNodes === 0) {\n      throw new Error(\"[OmDomDom]: Your element should have at least one root node.\");\n    } else {\n      return create(childNodes[0]);\n    }\n  }\n  var type = node.nodeType === 3 ? \"text\" : node.nodeType === 8 ? \"comment\" : node.tagName.toLowerCase();\n  var isSVG = isSVGContext || type === \"svg\";\n  var attributes = node.nodeType === 1 ? getAttributes(node) : {};\n  var content = numChildNodes > 0 ? null : node.textContent;\n  var children = Array(numChildNodes);\n  forEach(childNodes, function (child, idx) {\n    children[idx] = create(child, isSVG);\n  });\n  return {\n    type: type,\n    attributes: attributes,\n    children: children,\n    content: content,\n    node: node,\n    isSVGContext: isSVG\n  };\n};\n\nexport { create, patch, render };\n//# sourceMappingURL=omdomdom.es.js.map\n","export function takeWhileMap<T, A>(pred: (val: T) => A | undefined, lines: T[]): A[] {\n  var output = []\n  for (var line of lines) {\n    let a = pred(line)\n    if (a) output.push(a)\n    else break\n  }\n\n  return output\n}\n\nexport function dropWhile<T, A>(pred: (val: T) => A | undefined, lines: T[]): T[] {\n  let index = 0\n  while (index < lines.length && pred(lines[index])) {\n    index++\n  }\n  return lines.slice(index)\n}\n","import { takeWhileMap, dropWhile } from \"./lib\"\n\nexport type Meta = { key: string; value: string }\nexport type ViewId = string\nexport type RequestId = number\nexport type EncodedAction = string\nexport type ViewState = string\n\nexport type RemoteEvent = { name: string; detail: unknown }\n\nexport function renderMetas(meta: Meta[]): string {\n  return meta.map((m) => m.key + \": \" + m.value).join(\"\\n\")\n}\n\nexport type Metadata = {\n  cookies?: string[]\n  // redirect?: string\n  error?: string\n  query?: string\n  events?: RemoteEvent[]\n  actions?: [ViewId, EncodedAction][]\n  pageTitle?: string\n}\n\nexport function toMetadata(meta: Meta[]): Metadata {\n  return {\n    cookies: meta.filter((m) => m.key == \"Cookie\").map((m) => m.value),\n    // redirect: metaValue(\"Redirect\", meta),\n    error: metaValue(\"Error\", meta),\n    query: metaValue(\"Query\", meta),\n    pageTitle: metaValue(\"PageTitle\", meta),\n    events: metaValuesAll(\"Event\", meta).map(parseRemoteEvent),\n    actions: metaValuesAll(\"Trigger\", meta).map(parseAction),\n  }\n}\n\n// viewId: meta.find(m => m.key == \"VIEW-ID\")?.value,\n\nexport function parseMetadata(input: string): Metadata {\n  let metas = parseMetas(input.trim().split(\"\\n\"))\n  return toMetadata(metas)\n}\n\nexport function metaValue(key: string, metas: Meta[]): string | undefined {\n  return metas.find((m) => m.key == key)?.value\n}\n\nexport function metaValuesAll(key: string, metas: Meta[]): string[] {\n  return metas.filter((m) => m.key == key).map((m) => m.value)\n}\n\nexport type SplitMessage = {\n  command: string\n  metas: Meta[]\n  rest: string[]\n}\n\nexport function splitMessage(message: string): SplitMessage {\n  let lines = message.split(\"\\n\")\n  let command: string = lines[0]\n  let metas: Meta[] = takeWhileMap(parseMeta, lines.slice(1))\n  // console.log(\"Split Metadata\", lines.length)\n  // console.log(\" [0]\", lines[0])\n  // console.log(\" [1]\", lines[1])\n  let rest = dropWhile((l) => l == \"\", lines.slice(metas.length + 1))\n\n  return { command, metas, rest }\n}\n\nexport function parseMetas(lines: string[]): Meta[] {\n  return takeWhileMap(parseMeta, lines)\n}\n\nexport function parseMeta(line: string): Meta | undefined {\n  let match = line.match(/^(\\w+): (.*)$/)\n  if (match) {\n    return {\n      key: match[1],\n      value: match[2],\n    }\n  }\n}\n\nexport function parseRemoteEvent(input: string): RemoteEvent {\n  let [name, data] = breakNextSegment(input)\n  return {\n    name,\n    detail: JSON.parse(data),\n  }\n}\n\nexport function parseAction(input: string): [ViewId, string] {\n  let [viewId, action] = breakNextSegment(input)\n  return [viewId, action]\n}\n\nfunction breakNextSegment(input: string): [string, string] {\n  let ix = input.indexOf(\"|\")\n  if (ix === -1) {\n    let err = new Error(\"Bad Encoding, Expected Segment\")\n    err.message = input\n    throw err\n  }\n  return [input.slice(0, ix), input.slice(ix + 1)]\n}\n","import {\n  type Meta,\n  type ViewId,\n  type RequestId,\n  type ViewState,\n  type EncodedAction,\n} from \"./message\"\nimport * as message from \"./message\"\n\nexport type ActionMessage = {\n  viewId: ViewId\n  action: EncodedAction\n  requestId: RequestId\n  state?: ViewState\n  meta: Meta[]\n  form?: FormData\n  value?: InputValue\n}\n\nexport type InputValue = string\n\nexport type ActionBody = FormData | InputValue\n\nexport function actionMessage(\n  id: ViewId,\n  action: EncodedAction,\n  state: ViewState | undefined,\n  reqId: RequestId,\n  body?: ActionBody,\n): ActionMessage {\n  let meta: Meta[] = [\n    { key: \"Cookie\", value: decodeURI(document.cookie) },\n    { key: \"Query\", value: window.location.search },\n  ]\n\n  let form = undefined\n  let value = undefined\n  if (body instanceof FormData) {\n    form = body\n  } else if (body) {\n    value = body as string\n  }\n\n  return { viewId: id, action, state, requestId: reqId, meta, form, value }\n}\n\nexport function renderActionMessage(msg: ActionMessage): string {\n  let header = [\"|ACTION|\", \"ViewId: \" + msg.viewId, \"Action: \" + msg.action]\n\n  if (msg.state) {\n    header.push(\"State: \" + msg.state)\n  }\n\n  header.push(\"RequestId: \" + msg.requestId)\n\n  return [header.join(\"\\n\"), message.renderMetas(msg.meta)].join(\"\\n\") + renderInput(msg.value)\n  // Forms are submitted via fetch() + renderForm(msg.form)\n}\n\nexport function renderInput(value?: InputValue): string {\n  if (!value) return \"\"\n  return \"\\n\\n\" + value\n}\n\nlet globalRequestId: RequestId = 0\n\nexport type Request = {\n  requestId: RequestId\n  isCancelled: boolean\n}\n\nexport function newRequest(): Request {\n  let requestId = ++globalRequestId\n  return { requestId, isCancelled: false }\n}\n","import {\n  type ViewId,\n  type Metadata,\n  type Meta,\n  metaValue,\n  toMetadata,\n  type RequestId,\n  type EncodedAction,\n} from \"./message\"\n\nexport type ResponseBody = string\n\nexport function parseResponseDocument(res: ResponseBody): Document {\n  const parser = new DOMParser()\n  return parser.parseFromString(res, \"text/html\")\n}\n\nexport function documentUpdate(doc: Document): LiveUpdate {\n  const css = doc.querySelector<HTMLStyleElement>(\"style\")\n  const content = doc.querySelector<HTMLElement>(\"div\")\n\n  return {\n    content: content,\n    css: css,\n  }\n}\n\nexport type BaseResponse = {\n  requestId: RequestId\n  meta: Metadata\n  viewId: ViewId\n  targetViewId?: ViewId\n  action: EncodedAction\n  body: ResponseBody\n}\n\nexport type Response = BaseResponse & {\n  kind: \"response\"\n}\n\nexport type Update = BaseResponse & {\n  kind: \"update\"\n  targetViewId: ViewId\n}\n\nexport type Redirect = {\n  kind: \"redirect\"\n  requestId: RequestId\n  meta: Metadata\n  url: string\n}\n\nexport function parseUpdate(metas: Meta[], rest: string[]): Update {\n  let { requestId, viewId, action, meta, body } = parseResponse(metas, rest)\n\n  return {\n    kind: \"update\",\n    targetViewId: metaValue(\"TargetViewId\", metas) || viewId,\n    requestId,\n    viewId,\n    action,\n    meta,\n    body,\n  }\n}\n\nexport function parseResponse(metas: Meta[], rest: string[]): Response {\n  let viewId = requireMeta(rest, metas, \"ViewId\")\n  let action = requireMeta(rest, metas, \"Action\")\n  let requestId = parseInt(requireMeta(rest, metas, \"RequestId\"), 0)\n  return {\n    kind: \"response\",\n    requestId,\n    targetViewId: undefined, // only set on push update\n    viewId,\n    action,\n    meta: toMetadata(metas),\n    body: rest.join(\"\\n\"),\n  }\n}\n\nexport function parseRedirect(metas: Meta[], rest: string[]): Redirect {\n  let url = rest[0]\n  let requestId = parseInt(requireMeta(rest, metas, \"RequestId\"), 0)\n  return {\n    kind: \"redirect\",\n    requestId,\n    meta: toMetadata(metas),\n    url,\n  }\n}\n\nexport function requireMeta(lines: string[], metas: Meta[], key: string): string {\n  let val = metaValue(key, metas)\n  if (!val) throw new ProtocolError(\"Missing Required Metadata: \" + key, lines.join(\"\\n\"))\n  return val\n}\n\nexport type LiveUpdate = {\n  content: HTMLElement | null\n  css: HTMLStyleElement | null\n}\n\nexport class FetchError extends Error {\n  viewId: ViewId\n  body: string\n  constructor(viewId: ViewId, msg: string, body: string) {\n    super(msg)\n    this.viewId = viewId\n    this.name = \"Fetch Error\"\n    this.body = body\n  }\n}\n\nexport class ProtocolError extends Error {\n  constructor(description: string, body: string) {\n    super(description + \"\\n\" + body)\n    this.name = \"ProtocolError\"\n  }\n}\n","import { type ActionMessage, renderActionMessage } from \"./action\"\nimport { type ResponseBody, ProtocolError, type Response } from \"./response\"\nimport { dropWhile } from \"./lib\"\nimport * as message from \"./message\"\nimport {\n  type ViewId,\n  type RequestId,\n  type EncodedAction,\n  metaValue,\n  type Metadata,\n  type RemoteEvent,\n  type Meta,\n} from \"./message\"\n\nconst protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\"\nconst defaultAddress = `${protocol}//${window.location.host}${window.location.pathname}`\n\ninterface SocketConnectionEventMap {\n  update: CustomEvent<Update>\n  response: CustomEvent<Response>\n  redirect: CustomEvent<Redirect>\n  trigger: CustomEvent<Trigger>\n  event: CustomEvent<JSEvent>\n}\n\nexport class SocketConnection {\n  socket: WebSocket\n\n  hasEverConnected: boolean = false\n  isConnected: boolean = false\n  reconnectDelay: number = 0\n  queue: ActionMessage[] = []\n  events: EventTarget\n\n  constructor(addr = defaultAddress) {\n    this.events = new EventTarget()\n    const sock = new WebSocket(addr)\n    this.socket = sock\n  }\n\n  connect(addr = defaultAddress, createSocket = false) {\n    const sock = createSocket ? new WebSocket(addr) : this.socket\n    this.socket = sock\n\n    function onConnectError(ev: Event) {\n      console.error(\"Connect Error\", ev)\n    }\n\n    function onSocketError(ev: Event) {\n      console.error(\"Socket Error\", ev)\n    }\n\n    // initial connection errors\n    sock.addEventListener(\"error\", onConnectError)\n\n    sock.addEventListener(\"open\", (_event) => {\n      console.log(\"Websocket Connected\")\n\n      if (this.hasEverConnected) {\n        document.dispatchEvent(new Event(\"hyp-socket-reconnect\"))\n      }\n\n      this.isConnected = true\n      this.hasEverConnected = true\n      this.reconnectDelay = 1000\n      sock.removeEventListener(\"error\", onConnectError)\n      sock.addEventListener(\"error\", onSocketError)\n\n      document.dispatchEvent(new Event(\"hyp-socket-connect\"))\n\n      this.runQueue()\n    })\n\n    sock.addEventListener(\"close\", (_) => {\n      console.log(\"CLOSE SOCKET\")\n      if (this.isConnected) {\n        document.dispatchEvent(new Event(\"hyp-socket-disconnect\"))\n      }\n\n      this.isConnected = false\n      sock.removeEventListener(\"error\", onSocketError)\n\n      // attempt to reconnect in 1s\n      if (this.hasEverConnected) {\n        console.log(\"Reconnecting in \" + this.reconnectDelay / 1000 + \"s\")\n        setTimeout(() => this.connect(addr, true), this.reconnectDelay)\n      }\n\n      sock.removeEventListener(\"error\", onSocketError)\n    })\n\n    sock.addEventListener(\"message\", (ev) => this.onMessage(ev))\n  }\n\n  async sendAction(action: ActionMessage) {\n    if (this.isConnected) {\n      let msg = renderActionMessage(action)\n      this.socket.send(msg)\n    } else {\n      this.queue.push(action)\n    }\n  }\n\n  private runQueue() {\n    // send all messages queued while disconnected\n    let next: ActionMessage | undefined = this.queue.pop()\n    if (next) {\n      console.log(\"runQueue: \", next)\n      void this.sendAction(next)\n      this.runQueue()\n    }\n  }\n\n  // full responses will never be sent over!\n  private onMessage(event: MessageEvent) {\n    let { command, metas, rest } = splitMessage(event.data)\n    // console.log(\"MESSAGE\", command, metas, rest)\n\n    let requestId = parseInt(requireMeta(\"RequestId\"), 0)\n\n    function requireMeta(key: string): string {\n      let val = metaValue(key, metas)\n      if (!val) throw new ProtocolError(\"Missing Required Metadata: \" + key, event.data)\n      return val\n    }\n\n    function parseResponse(rest: string[]): Update {\n      let viewId = requireMeta(\"ViewId\")\n      let action = requireMeta(\"Action\")\n      return {\n        requestId,\n        targetViewId: undefined,\n        viewId,\n        action,\n        meta: message.toMetadata(metas),\n        body: rest.join(\"\\n\"),\n      }\n    }\n\n    function parseUpdate(rest: string[]): Update {\n      let up = parseResponse(rest)\n      // add the TargetViewId\n      up.targetViewId = metaValue(\"TargetViewId\", metas)\n      return up\n    }\n\n    function parseRedirect(rest: string[]): Redirect {\n      let url = rest[0]\n      return {\n        requestId,\n        meta: message.toMetadata(metas),\n        url,\n      }\n    }\n\n    function parseTrigger(rest: string[]): Trigger {\n      let { requestId, meta, viewId, action } = parseResponse(rest)\n      let [targetViewId, targetAction] = message.parseAction(requireMeta(\"Trigger\"))\n      return { requestId, meta, viewId, action, targetViewId, targetAction }\n    }\n\n    function parseEvent(rest: string[]): JSEvent {\n      let { requestId, meta, viewId, action } = parseResponse(rest)\n      let event = message.parseRemoteEvent(requireMeta(\"Event\"))\n      return { requestId, meta, viewId, action, event }\n    }\n\n    switch (command) {\n      case \"|UPDATE|\":\n        this.dispatchEvent(new CustomEvent<Update>(\"update\", { detail: parseUpdate(rest) }))\n        return\n\n      case \"|RESPONSE|\":\n        this.dispatchEvent(new CustomEvent<Update>(\"response\", { detail: parseResponse(rest) }))\n        return\n\n      case \"|REDIRECT|\":\n        return this.dispatchEvent(new CustomEvent(\"redirect\", { detail: parseRedirect(rest) }))\n\n      case \"|TRIGGER|\":\n        return this.dispatchEvent(new CustomEvent(\"trigger\", { detail: parseTrigger(rest) }))\n\n      case \"|EVENT|\":\n        return this.dispatchEvent(new CustomEvent(\"event\", { detail: parseEvent(rest) }))\n\n      default:\n        throw new ProtocolError(\"Unknown Server Command: \" + command, event.data)\n    }\n  }\n\n  // so what if they send remote events in the page? trigger, redirect, page title, etc...\n  // we aren't connected yet on a page thing\n\n  // private async waitMessage(reqId: RequestId, id: ViewId): Promise<ParsedResponse> {\n  //   return new Promise((resolve, reject) => {\n  //     const onMessage = (event: MessageEvent) => {\n  //       let data: string = event.data\n  //       let lines = data.split(\"\\n\").slice(1)  // drop the command line\n  //\n  //       let parsed = splitMetadata(lines)\n  //       let metadata: Metadata = parsed.metadata\n  //\n  //       if (!metadata.requestId) {\n  //         console.error(\"Missing RequestId!\", metadata, event.data)\n  //         return\n  //       }\n  //\n  //       if (metadata.requestId != reqId) {\n  //         // skip, it's not us!\n  //         return\n  //       }\n  //\n  //\n  //       // We have found our message. Remove the listener\n  //       this.socket.removeEventListener('message', onMessage)\n  //\n  //       // set the cookies. These happen automatically in http\n  //       metadata.cookies.forEach((cookie: string) => {\n  //         document.cookie = cookie\n  //       })\n  //\n  //       if (metadata.error) {\n  //         reject(new FetchError(id, metadata.error, parsed.rest.join('\\n')))\n  //         return\n  //       }\n  //\n  //       resolve(parsed)\n  //     }\n  //\n  //     this.socket.addEventListener('message', onMessage)\n  //     this.socket.addEventListener('error', reject)\n  //   })\n  // }\n\n  addEventListener<K extends keyof SocketConnectionEventMap>(\n    e: K,\n    cb: (ev: SocketConnectionEventMap[K]) => void,\n  ) {\n    this.events.addEventListener(\n      e,\n      // @ts-ignore: HACK\n      cb,\n    )\n  }\n\n  dispatchEvent<K extends keyof SocketConnectionEventMap>(e: SocketConnectionEventMap[K]) {\n    this.events.dispatchEvent(e)\n  }\n\n  disconnect() {\n    this.isConnected = false\n    this.hasEverConnected = false\n    this.socket.close()\n  }\n}\n\nexport type Update = {\n  requestId: RequestId\n  meta: Metadata\n  viewId: ViewId\n  targetViewId?: ViewId\n  action: EncodedAction\n  body: ResponseBody\n}\n\nexport type Redirect = {\n  requestId: RequestId\n  meta: Metadata\n  url: string\n}\n\nexport type Trigger = {\n  requestId: RequestId\n  meta: Metadata\n  viewId: ViewId\n  action: EncodedAction\n  targetViewId: ViewId\n  targetAction: string\n}\n\nexport type JSEvent = {\n  requestId: RequestId\n  meta: Metadata\n  viewId: ViewId\n  action: EncodedAction\n  event: RemoteEvent\n}\n\nexport type MessageType = string\n\nexport type SplitMessage = {\n  command: string\n  metas: Meta[]\n  rest: string[]\n}\n\nexport function splitMessage(msg: string): SplitMessage {\n  let lines = msg.split(\"\\n\")\n  let command: string = lines[0]\n  let metas: Meta[] = message.parseMetas(lines.slice(1))\n  // console.log(\"Split Metadata\", lines.length)\n  // console.log(\" [0]\", lines[0])\n  // console.log(\" [1]\", lines[1])\n  let rest = dropWhile((l) => l == \"\", lines.slice(metas.length + 1))\n\n  return { command, metas, rest }\n}\n","export default function debounce(function_, wait = 100, options = {}) {\n\tif (typeof function_ !== 'function') {\n\t\tthrow new TypeError(`Expected the first parameter to be a function, got \\`${typeof function_}\\`.`);\n\t}\n\n\tif (wait < 0) {\n\t\tthrow new RangeError('`wait` must not be negative.');\n\t}\n\n\tif (typeof options === 'boolean') {\n\t\tthrow new TypeError('The `options` parameter must be an object, not a boolean. Use `{immediate: true}` instead.');\n\t}\n\n\tconst {immediate} = options;\n\n\tlet storedContext;\n\tlet storedArguments;\n\tlet timeoutId;\n\tlet timestamp;\n\tlet result;\n\n\tfunction run() {\n\t\tconst callContext = storedContext;\n\t\tconst callArguments = storedArguments;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t\tresult = function_.apply(callContext, callArguments);\n\t\treturn result;\n\t}\n\n\tfunction later() {\n\t\tconst last = Date.now() - timestamp;\n\n\t\tif (last < wait && last >= 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t} else {\n\t\t\ttimeoutId = undefined;\n\n\t\t\tif (!immediate) {\n\t\t\t\tresult = run();\n\t\t\t}\n\t\t}\n\t}\n\n\tconst debounced = function (...arguments_) {\n\t\tif (\n\t\t\tstoredContext\n\t\t\t&& this !== storedContext\n\t\t\t&& Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)\n\t\t) {\n\t\t\tthrow new Error('Debounced method called with different contexts of the same prototype.');\n\t\t}\n\n\t\tstoredContext = this; // eslint-disable-line unicorn/no-this-assignment\n\t\tstoredArguments = arguments_;\n\t\ttimestamp = Date.now();\n\n\t\tconst callNow = immediate && !timeoutId;\n\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\n\t\tif (callNow) {\n\t\t\tresult = run();\n\t\t\treturn result;\n\t\t}\n\n\t\treturn undefined;\n\t};\n\n\tObject.defineProperty(debounced, 'isPending', {\n\t\tget() {\n\t\t\treturn timeoutId !== undefined;\n\t\t},\n\t});\n\n\tdebounced.clear = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(timeoutId);\n\t\ttimeoutId = undefined;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t};\n\n\tdebounced.flush = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tdebounced.trigger();\n\t};\n\n\tdebounced.trigger = () => {\n\t\tresult = run();\n\n\t\tdebounced.clear();\n\t};\n\n\treturn debounced;\n}\n","import { type Request, type ActionBody } from \"./action\"\n\nexport interface HyperView extends HTMLElement {\n  runAction(action: string, body?: ActionBody): Promise<void>\n  activeRequest?: Request\n  cancelActiveRequest(): void\n  concurrency: ConcurrencyMode\n  _timeout?: number\n}\n\nexport const isHyperView = (ele: any): ele is HyperView => {\n  return ele?.runAction !== undefined\n}\n\nexport type ConcurrencyMode = string\n\nexport function dispatchContent(node: HTMLElement): void {\n  let event = new Event(\"hyp-content\", { bubbles: true })\n  node.dispatchEvent(event)\n}\n\nexport function enrichHyperViews(\n  node: HTMLElement,\n  runAction: (target: HyperView, action: string, body: ActionBody) => Promise<void>,\n): void {\n  // enrich all the hyperviews\n  node.querySelectorAll<HyperView>(\"[id]\").forEach((element) => {\n    element.runAction = function (action: string, body: ActionBody) {\n      return runAction(element, action, body)\n    }\n\n    element.concurrency = element.dataset.concurrency || \"Drop\"\n\n    element.cancelActiveRequest = function () {\n      if (element.activeRequest && !element.activeRequest?.isCancelled) {\n        element.activeRequest.isCancelled = true\n      }\n    }\n\n    dispatchContent(node)\n  })\n}\n","import debounce from \"debounce\"\nimport { type InputValue } from \"./action\"\nimport { isHyperView, type HyperView } from \"./hyperview\"\n\nexport type UrlFragment = string\n\nexport function listenKeydown(cb: (target: HyperView, action: string) => void): void {\n  listenKeyEvent(\"keydown\", cb)\n}\n\nexport function listenKeyup(cb: (target: HyperView, action: string) => void): void {\n  listenKeyEvent(\"keyup\", cb)\n}\n\nexport function listenKeyEvent(\n  event: \"keyup\" | \"keydown\",\n  cb: (target: HyperView, action: string) => void,\n): void {\n  document.addEventListener(event, function (e: KeyboardEvent) {\n    if (!(e.target instanceof HTMLElement)) {\n      console.warn(\"listenKeyEvent received event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let source = e.target\n\n    let datasetKey = \"on\" + event + e.key\n    let action = source.dataset[datasetKey]\n    if (!action) return\n\n    e.preventDefault()\n    const target = nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: \", source)\n      return\n    }\n    cb(target, action)\n  })\n}\n\nexport function listenBubblingEvent(\n  event: string,\n  cb: (_target: HyperView, action: string) => void,\n): void {\n  document.addEventListener(event, function (e) {\n    if (!(e.target instanceof Element)) {\n      console.warn(\"listenBubblingEvent received an event with non Elment as EventTarget: %o\", e)\n      return\n    }\n    let el = e.target\n\n    // clicks can fire on internal elements. Find the parent with a click handler\n    let source = el.closest<HTMLElement>(\"[data-on\" + event + \"]\")\n    if (!source) return\n\n    e.preventDefault()\n    let target = nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: \", source)\n      return\n    }\n    const action = source.dataset[\"on\" + event]\n    if (action === undefined) {\n      console.error(\"Missing action: \", source, event)\n      return\n    }\n    cb(target, action)\n  })\n}\n\nexport function listenClick(cb: (target: HyperView, action: string) => void): void {\n  listenBubblingEvent(\"click\", cb)\n}\n\nexport function listenDblClick(cb: (target: HyperView, action: string) => void): void {\n  listenBubblingEvent(\"dblclick\", cb)\n}\n\nexport function listenTopLevel(cb: (target: HyperView, action: string) => void): void {\n  document.addEventListener(\"hyp-load\", function (e: CustomEvent) {\n    let action = e.detail.onLoad\n    let target = e.detail.target\n    cb(target, action)\n  })\n\n  document.addEventListener(\"hyp-mouseenter\", function (e: CustomEvent) {\n    let action = e.detail.onMouseEnter\n    let target = e.detail.target\n    cb(target, action)\n  })\n\n  document.addEventListener(\"hyp-mouseleave\", function (e: CustomEvent) {\n    let action = e.detail.onMouseLeave\n    let target = e.detail.target\n    cb(target, action)\n  })\n}\n\nexport function listenLoad(node: HTMLElement): void {\n  // it doesn't really matter WHO runs this except that it should have target\n  node.querySelectorAll<HTMLElement>(\"[data-onload]\").forEach((load) => {\n    let delay = parseInt(load.dataset.delay || \"\") || 0\n    let onLoad = load.dataset.onload\n    // console.log(\"load start\", load.dataset.onLoad)\n\n    // load no longer exists!\n    // we should clear the timeout or back out if the dom is replaced in the interem\n    setTimeout(() => {\n      let target = nearestHyperViewTarget(load)\n      // console.log(\"load go\", load.dataset.onLoad)\n\n      if (load.dataset.onload != onLoad) {\n        // the onLoad no longer exists\n        return\n      }\n\n      const event = new CustomEvent(\"hyp-load\", { bubbles: true, detail: { target, onLoad } })\n      load.dispatchEvent(event)\n    }, delay)\n  })\n}\n\nexport function listenMouseEnter(node: HTMLElement): void {\n  node.querySelectorAll<HTMLElement>(\"[data-onmouseenter]\").forEach((node) => {\n    let onMouseEnter = node.dataset.onmouseenter\n\n    let target = nearestAnyTarget(node)\n\n    node.onmouseenter = () => {\n      const event = new CustomEvent(\"hyp-mouseenter\", {\n        bubbles: true,\n        detail: { target, onMouseEnter },\n      })\n      node.dispatchEvent(event)\n    }\n  })\n}\n\nexport function listenMouseLeave(node: HTMLElement): void {\n  node.querySelectorAll<HTMLElement>(\"[data-onmouseleave]\").forEach((node) => {\n    let onMouseLeave = node.dataset.onmouseleave\n\n    let target = nearestAnyTarget(node)\n\n    node.onmouseleave = () => {\n      const event = new CustomEvent(\"hyp-mouseleave\", {\n        bubbles: true,\n        detail: { target, onMouseLeave },\n      })\n      node.dispatchEvent(event)\n    }\n  })\n}\n\nexport function listenChange(cb: (target: HyperView, action: string, value: string) => void): void {\n  document.addEventListener(\"change\", function (e) {\n    if (!(e.target instanceof HTMLElement)) {\n      console.warn(\"listenChange received an event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let el = e.target\n\n    let source = el.closest<HTMLInputElement>(\"[data-onchange]\")\n\n    if (!source) return\n    e.preventDefault()\n\n    if (source.value === null) {\n      console.error(\"Missing input value:\", source)\n      return\n    }\n\n    let target = nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: listenChange\")\n      return\n    }\n\n    if (!source.dataset.onchange) {\n      console.error(\"Missing onchange: \", source)\n      return\n    }\n\n    cb(target, source.dataset.onchange, source.value)\n  })\n}\n\ninterface LiveInputElement extends HTMLInputElement {\n  debouncedCallback?: Function\n}\n\nexport function listenInput(\n  startedTyping: (target: HyperView) => void,\n  cb: (target: HyperView, action: string, value: InputValue) => void,\n): void {\n  document.addEventListener(\"input\", function (e) {\n    if (!(e.target instanceof HTMLElement)) {\n      console.warn(\"listenInput received an event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let el = e.target\n    const source = el.closest<LiveInputElement>(\"[data-oninput]\")\n\n    if (!source) return\n\n    let delay = parseInt(source.dataset.delay || \"\") || 250\n    if (delay < 250) {\n      console.warn(\"Input delay < 250 can result in poor performance.\")\n    }\n\n    e.preventDefault()\n\n    const target = nearestHyperViewTarget(source)\n    if (!target) {\n      console.error(\"Missing target: \", source)\n      return\n    }\n\n    // I want to CANCEL the active request as soon as we start typing\n    startedTyping(target)\n\n    if (!source.debouncedCallback) {\n      source.debouncedCallback = debounce(() => {\n        if (!source.dataset.oninput) {\n          console.error(\"Missing onInput: \", source)\n          return\n        }\n\n        cb(target, source.dataset.oninput, source.value)\n      }, delay)\n    }\n\n    source.debouncedCallback()\n  })\n}\n\nexport function listenFormSubmit(\n  cb: (target: HyperView, action: string, form: FormData) => void,\n): void {\n  document.addEventListener(\"submit\", function (e) {\n    if (!(e.target instanceof HTMLFormElement)) {\n      console.warn(\"listenFormSubmit received an event with non HTMLElment as EventTarget: %o\", e)\n      return\n    }\n    let form = e.target\n\n    if (!form.dataset.onsubmit) {\n      console.error(\"Missing onSubmit: \", form)\n      return\n    }\n\n    e.preventDefault()\n\n    let target = nearestHyperViewTarget(form)\n    const formData = new FormData(form)\n    if (!target) {\n      console.error(\"Missing target: \", target)\n      return\n    }\n    cb(target, form.dataset.onsubmit, formData)\n  })\n}\n\nfunction nearestTargetId(node: HTMLElement): string | undefined {\n  let targetData = node.closest<HTMLElement>(\"[data-target]\")\n  return targetData?.dataset.target || node.closest(\"[id]\")?.id\n}\n\nfunction nearestHyperViewTarget(node: HTMLElement): HyperView | undefined {\n  const target = nearestAnyTarget(node)\n\n  if (!isHyperView(target)) {\n    console.error(\"Non HyperView target: \", target)\n    return\n  }\n\n  return target\n}\n\nfunction nearestAnyTarget(node: HTMLElement): HTMLElement | undefined {\n  let targetId = nearestTargetId(node)\n  let target = targetId && document.getElementById(targetId)\n\n  if (!target) {\n    console.error(\"Cannot find target: \", targetId, node)\n    return\n  }\n\n  return target\n}\n","export function setQuery(query: string) {\n  if (query != currentQuery()) {\n    if (query != \"\") query = \"?\" + query\n    let url = location.pathname + query\n    // console.log(\"history.replaceState(\", url, \")\")\n    window.history.replaceState({}, \"\", url)\n  }\n}\n\nfunction currentQuery(): string {\n  const query = window.location.search\n  return query.startsWith(\"?\") ? query.substring(1) : query\n}\n","import { type ActionMessage } from \"./action\"\nimport { type Response, type Redirect, parseResponse } from \"./response\"\nimport { type Meta, parseMetas } from \"./message\"\n\n// Could be a redirect or response\nexport async function sendActionHttp(msg: ActionMessage): Promise<Response | Redirect> {\n  // console.log(\"HTTP sendAction\", msg.url.toString())\n  let url = window.location.href\n\n  let headers: any = {\n    Accept: \"text/html\",\n    \"Hyp-ViewId\": msg.viewId,\n    \"Hyp-RequestId\": msg.requestId,\n    \"Hyp-Action\": msg.action,\n    \"Hyp-State\": msg.state || \"()\",\n  }\n\n  console.log(\"sendActionHttp\", msg.form)\n\n  let res = await fetch(url, {\n    method: \"POST\",\n    headers: headers,\n    body: msg.form,\n    // we never want this to be redirected\n    redirect: \"manual\",\n  })\n\n  let body = await res.text()\n\n  // TODO: handle redirects. Check status code and do it\n  // if (!res.ok) {\n  //   throw new FetchError(msg.viewId, \"Invalid Status:\" + res.status, body)\n  // }\n\n  const { metas, rest } = splitResponse(body)\n  const response = parseResponse(metas, rest)\n  return response\n}\n\nexport function splitResponse(body: string): SplitResponse {\n  let lines = body.split(\"\\n\")\n  // drop the <script> start line\n  let metas = parseMetas(lines.slice(1))\n  let rest = lines.slice(2)\n  // drop the </script> end line and 2x whitespace\n  return { metas, rest }\n}\n\nexport type SplitResponse = {\n  metas: Meta[]\n  rest: string[]\n}\n\n// export function documentScriptMeta(doc: Document): Metadata {\n//   const meta = doc.querySelector<HTMLScriptElement>(\"script#hyp.metadata\")\n//   return parseMetadata(meta?.textContent || \"\")\n// }\n","import { patch, create } from \"omdomdom/lib/omdomdom.es.js\"\nimport { SocketConnection, type Update, type Redirect, type Trigger, type JSEvent } from \"./sockets\"\nimport {\n  listenChange,\n  listenClick,\n  listenDblClick,\n  listenFormSubmit,\n  listenLoad,\n  listenTopLevel,\n  listenInput,\n  listenKeydown,\n  listenKeyup,\n  listenMouseEnter,\n  listenMouseLeave,\n} from \"./events\"\nimport { actionMessage, newRequest, type ActionBody, type InputValue } from \"./action\"\nimport {\n  type ViewId,\n  type Metadata,\n  parseMetadata,\n  type RemoteEvent,\n  type EncodedAction,\n} from \"./message\"\nimport { setQuery } from \"./browser\"\nimport {\n  parseResponseDocument,\n  type LiveUpdate,\n  type Response,\n  type BaseResponse,\n  documentUpdate,\n} from \"./response\"\nimport { dispatchContent, enrichHyperViews, type HyperView, isHyperView } from \"./hyperview\"\nimport { sendActionHttp } from \"./http\"\n\ndeclare const __VERSION__: string\n\n// console.log(\"VERSION 2\", INIT_PAGE, INIT_STATE)\nconsole.log(\"Hyperbole \" + __VERSION__ + \"b\")\n\nlet rootStyles: HTMLStyleElement\nlet addedRulesIndex = new Set()\n\n// Run an action in a given HyperView\nasync function runAction(target: HyperView, action: string, body?: ActionBody) {\n  if (target.activeRequest && !target.activeRequest?.isCancelled) {\n    // Active Request!\n    if (target.concurrency == \"Drop\") {\n      console.warn(\"Drop action overlapping with active request\", target.activeRequest, action)\n      return\n    }\n  }\n\n  target._timeout = window.setTimeout(() => {\n    // add loading after 100ms, not right away\n    // if it runs shorter than that we probably don't want to show the user any loading feedback\n    target.classList.add(\"hyp-loading\")\n  }, 100)\n\n  let state = target.dataset.state\n\n  let req = newRequest()\n  let msg = actionMessage(target.id, action, state, req.requestId, body)\n\n  // Set the requestId\n  target.activeRequest = req\n\n  if (!msg.form) {\n    // If we have a form, send via HTTP instead of socket\n    // NOTE: this disables push update!\n    void sock.sendAction(msg)\n  } else {\n    let res = await sendActionHttp(msg)\n    console.log(\"RESPONSE\", res)\n\n    // TODO: redirect on http\n    // TODO: forward errors to view. Right now they throw!\n    switch (res.kind) {\n      case \"redirect\":\n        console.log(\"TODO HANDLE REDIRECT\")\n        break\n      case \"response\":\n        handleResponse(res)\n        break\n    }\n  }\n}\n\nfunction handleTrigger(trigger: Trigger) {\n  runTrigger(trigger.targetViewId, trigger.targetAction)\n}\n\nfunction handleEvent(ev: JSEvent) {\n  let target = document.getElementById(ev.viewId)\n  runRemoteEvent(ev.event, target)\n}\n\n// TODO: redirect concurrency\nfunction handleRedirect(red: Redirect) {\n  console.log(\"REDIRECT\", red)\n\n  // the other metdata doesn't apply, they are all specific to the page\n  applyCookies(red.meta.cookies ?? [])\n\n  window.location.href = red.url\n}\n\n// in-process update\nfunction handleResponse(res: Response) {\n  // console.log(\"Handle Response\", res)\n  let target = runUpdate(res.viewId, res)\n  if (!target) return\n\n  // clean up the request\n  delete target.activeRequest\n  clearTimeout(target._timeout)\n  target.classList.remove(\"hyp-loading\")\n}\n\nfunction handleUpdate(up: Update) {\n  if (up.meta.error) {\n    runUpdate(up.viewId, up)\n  } else if (!up.targetViewId) {\n    console.error(\"Missing Update targetViewId\", up)\n  } else {\n    runUpdate(up.targetViewId, up)\n  }\n}\n\nfunction runUpdate(targetViewId: ViewId, res: BaseResponse): HyperView | undefined {\n  // console.log(\"|UPDATE|\", res)\n\n  // let targetViewId = res.targetViewId || res.viewId\n  let target = document.getElementById(targetViewId)\n\n  if (!isHyperView(target)) {\n    console.error(\"Missing Update HyperView Target: \", targetViewId, res)\n    return\n  }\n\n  if (target.activeRequest?.requestId && res.requestId < target.activeRequest.requestId) {\n    // this should only happen on Replace, since other requests should be dropped\n    // but it's safe to assume we never want to apply an old requestId\n    console.warn(\n      \"Ignore Stale Action (\" +\n        res.requestId +\n        \") vs (\" +\n        target.activeRequest.requestId +\n        \"): \" +\n        res.action,\n    )\n    return target\n  } else if (target.activeRequest?.isCancelled) {\n    console.warn(\"Cancelled request\", target.activeRequest?.requestId)\n    delete target.activeRequest\n    return target\n  }\n\n  let update: LiveUpdate = documentUpdate(parseResponseDocument(res.body))\n\n  if (!update.content) {\n    console.error(\"Empty Response!\", res.body)\n    return target\n  }\n\n  // First, update the stylesheet\n  addCSS(update.css)\n\n  // Patch the node\n  const old: VNode = create(target)\n  let next: VNode = create(update.content)\n  let atts = next.attributes\n\n  if (!res.meta.error && atts[\"id\"] != target.id) {\n    console.error(\"Mismatched ViewId in update - \", atts[\"id\"], \" target:\", target.id)\n    return\n  }\n\n  let state = atts[\"data-state\"]\n  next.attributes = old.attributes\n\n  patch(next, old)\n\n  // Emit relevant events\n  let newTarget = document.getElementById(target.id)\n\n  if (!newTarget) {\n    console.warn(\"Target Missing: \", target.id)\n    return target\n  }\n\n  dispatchContent(newTarget)\n\n  // re-add state attribute\n  if (state === undefined || state == \"()\") delete newTarget.dataset.state\n  else newTarget.dataset.state = state\n\n  // execute the metadata, anything that doesn't interrupt the dom update\n  runMetadata(res.meta, newTarget)\n  applyCookies(res.meta.cookies ?? [])\n\n  // now way for these to bubble)\n  listenLoad(newTarget)\n  listenMouseEnter(newTarget)\n  listenMouseLeave(newTarget)\n  fixInputs(newTarget)\n  enrichHyperViews(newTarget, runAction)\n\n  return target\n}\n// catch (err) {\n//   console.error(\"Caught Error in HyperView (\" + target.id + \"):\\n\", err)\n//\n//   // Hyperbole catches handler errors, and the server controls what to display to the user on an error\n//   //  but if you manage to crash your parent server process somehow, the response may be empty\n//   target.innerHTML = err.body || \"<div style='background:red;color:white;padding:10px'>Hyperbole Internal Error</div>\"\n// }\n\nfunction applyCookies(cookies: string[]) {\n  cookies.forEach((cookie: string) => {\n    console.log(\"SetCookie: \", cookie)\n    document.cookie = cookie\n  })\n}\n\nfunction runMetadata(meta: Metadata, target: HTMLElement | null) {\n  if (meta.query != null) {\n    setQuery(meta.query)\n  }\n\n  if (meta.pageTitle != null) {\n    document.title = meta.pageTitle\n  }\n\n  meta.events?.forEach((remoteEvent) => {\n    runRemoteEvent(remoteEvent, target)\n  })\n\n  meta.actions?.forEach(([viewId, action]) => {\n    runTrigger(viewId, action)\n  })\n}\n\nfunction runRemoteEvent(remoteEvent: RemoteEvent, target: HTMLElement | null) {\n  setTimeout(() => {\n    let event = new CustomEvent(remoteEvent.name, { bubbles: true, detail: remoteEvent.detail })\n    let eventTarget = target || document\n    eventTarget.dispatchEvent(event)\n  }, 10)\n}\n\nfunction runTrigger(viewId: ViewId, action: EncodedAction) {\n  setTimeout(() => {\n    let view = window.Hyperbole?.hyperView(viewId)\n    if (view) {\n      void runAction(view, action, undefined)\n    }\n  }, 10)\n}\n\nfunction fixInputs(target: HTMLElement) {\n  let focused = target.querySelector<HTMLInputElement>(\"[autofocus]\")\n  if (focused?.focus) {\n    focused.focus()\n  }\n\n  target.querySelectorAll<HTMLInputElement>(\"input[value]\").forEach((input) => {\n    let val = input.getAttribute(\"value\")\n    if (val !== null) {\n      input.value = val\n    }\n  })\n\n  target.querySelectorAll<HTMLInputElement>(\"input[type=checkbox]\").forEach((checkbox) => {\n    let checked = checkbox.dataset.checked == \"True\"\n    checkbox.checked = checked\n  })\n}\n\nfunction addCSS(src: HTMLStyleElement | null) {\n  if (!src) return\n  const rules = src.sheet?.cssRules\n  if (!rules) return\n  for (let i = 0; i < rules.length; i++) {\n    const rule = rules.item(i)\n    if (rule && addedRulesIndex.has(rule.cssText) == false && rootStyles.sheet) {\n      rootStyles.sheet.insertRule(rule.cssText)\n      addedRulesIndex.add(rule.cssText)\n    }\n  }\n}\n\nfunction init() {\n  // metadata attached to initial page loads need to be executed\n  let meta = parseMetadata(document.getElementById(\"hyp.metadata\")?.innerText ?? \"\")\n  // runMetadataImmediate(meta)\n  runMetadata(meta, null)\n\n  const style = document.body.querySelector(\"style\")\n\n  if (style !== null) {\n    rootStyles = style\n  } else {\n    console.warn(\"rootStyles missing from page, creating...\")\n    rootStyles = document.createElement(\"style\")\n    rootStyles.type = \"text/css\"\n    document.body.appendChild(rootStyles)\n  }\n\n  listenTopLevel(async function (target: HyperView, action: string) {\n    void runAction(target, action, undefined)\n  })\n\n  listenLoad(document.body)\n  listenMouseEnter(document.body)\n  listenMouseLeave(document.body)\n  enrichHyperViews(document.body, runAction)\n\n  listenClick(async function (target: HyperView, action: string) {\n    // console.log(\"CLICK\", target.id, action)\n    void runAction(target, action, undefined)\n  })\n\n  listenDblClick(async function (target: HyperView, action: string) {\n    // console.log(\"DBLCLICK\", target.id, action)\n    void runAction(target, action, undefined)\n  })\n\n  listenKeydown(async function (target: HyperView, action: string) {\n    // console.log(\"KEYDOWN\", target.id, action)\n    void runAction(target, action, undefined)\n  })\n\n  listenKeyup(async function (target: HyperView, action: string) {\n    // console.log(\"KEYUP\", target.id, action)\n    void runAction(target, action)\n  })\n\n  listenFormSubmit(async function (target: HyperView, action: string, form: FormData) {\n    // console.log(\"FORM\", target.id, action, form)\n    void runAction(target, action, form)\n  })\n\n  listenChange(async function (target: HyperView, action: string, value: string) {\n    void runAction(target, action, value)\n  })\n\n  function onStartedTyping(target: HyperView) {\n    if (target.concurrency == \"Replace\") {\n      target.cancelActiveRequest()\n    }\n  }\n\n  listenInput(\n    onStartedTyping,\n    async function (target: HyperView, action: string, value: InputValue) {\n      void runAction(target, action, value)\n    },\n  )\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", init)\n\nconst sock = new SocketConnection()\n// Should we connect to the socket or not?\nsock.connect()\nsock.addEventListener(\"update\", (ev: CustomEvent<Update>) => handleUpdate(ev.detail))\nsock.addEventListener(\"response\", (ev: CustomEvent<Response>) => handleResponse(ev.detail))\nsock.addEventListener(\"redirect\", (ev: CustomEvent<Redirect>) => handleRedirect(ev.detail))\nsock.addEventListener(\"trigger\", (ev: CustomEvent<Trigger>) => handleTrigger(ev.detail))\nsock.addEventListener(\"event\", (ev: CustomEvent<JSEvent>) => handleEvent(ev.detail))\n\ntype VNode = {\n  // One of three value types are used:\n  // - The tag name of the element\n  // - \"text\" if text node\n  // - \"comment\" if comment node\n  type: string\n\n  // An object whose key/value pairs are the attribute\n  // name and value, respectively\n  attributes: { [key: string]: string | undefined }\n\n  // Is set to `true` if a node is an `svg`, which tells\n  // Omdomdom to treat it, and its children, as such\n  isSVGContext: boolean\n\n  // The content of a \"text\" or \"comment\" node\n  content: string\n\n  // An array of virtual node children\n  children: Array<VNode>\n\n  // The real DOM node\n  node: Node\n}\n\ndeclare global {\n  interface Window {\n    Hyperbole?: HyperboleAPI\n  }\n  interface DocumentEventMap {\n    \"hyp-load\": CustomEvent\n    \"hyp-mouseenter\": CustomEvent\n    \"hyp-mouseleave\": CustomEvent\n  }\n}\n\nexport interface HyperboleAPI {\n  runAction(target: HTMLElement, action: string, body?: ActionBody): Promise<void>\n  action(con: string, ...params: any[]): string\n  hyperView(viewId: ViewId): HyperView | undefined\n  parseMetadata(input: string): Metadata\n  socket: SocketConnection\n}\n\nwindow.Hyperbole = {\n  runAction: runAction,\n  parseMetadata: parseMetadata,\n  action: function (con, ...params: any[]) {\n    return params.reduce((str, param) => str + \" \" + JSON.stringify(param), con)\n  },\n  hyperView: function (viewId) {\n    let element = document.getElementById(viewId)\n    if (!isHyperView(element)) {\n      console.error(\"Element id=\" + viewId + \" was not a HyperView\")\n      return\n    }\n    return element\n  },\n  socket: sock,\n}\n"],"x_google_ignoreList":[0,6],"mappings":"YAKA,IAAI,EAAuB,WACvB,EAAc,SAAqB,EAAK,EAAM,CAChD,OAAO,OAAO,UAAU,eAAe,KAAK,EAAK,EAAK,EAEpD,EAAa,SAAoB,EAAK,EAAK,CAM7C,OALK,EACD,EAAY,EAAK,EAAI,EACvB,QAAQ,KAAK,8KAA8K,CACpL,IAEF,GALU,IAOf,EAAU,SAAiB,EAAO,EAAI,CACxC,IAAI,EAAS,EAAM,OACf,EAAM,GACL,KACL,KAAO,EAAE,EAAM,GACT,EAAG,EAAM,GAAM,EAAI,GAAK,OAG5B,EAAe,SAAsB,EAAU,CACjD,IAAI,EAAM,EAAE,CAOZ,IAAK,IAAI,KANT,EAAQ,EAAU,SAAU,EAAO,CACjC,IAAI,EAAM,EAAM,WAAW,GACvB,EAAW,EAAK,EAAI,GACtB,EAAI,GAAO,IAEb,CACY,EACZ,OAAO,GAGP,EAAe,SAAsB,EAAQ,EAAO,EAAS,CAC/D,OAAO,EAAO,KAAK,aAAa,EAAM,KAAM,EAAQ,EAElD,EAAc,SAAqB,EAAU,EAAO,CACtD,IAAK,IAAI,KAAY,EACnB,EAAM,GAAY,EAAS,IAI3B,EAAS,SAAgB,EAAY,CACvC,IAAI,EAAqB,EAAW,MAAM,CAAC,QAAQ,QAAS,IAAI,CAAC,QAAQ,QAAS,IAAI,CAGtF,OADc,IADG,WACG,CAAC,gBAAgB,EAAoB,YAC3C,CAAC,MAGjB,SAAS,EAAsB,EAAK,EAAG,CACrC,IAAI,EAAa,GAAR,KAAc,KAAsB,OAAO,OAAtB,KAAgC,EAAI,OAAO,WAAa,EAAI,cAC1F,GAAY,GAAR,KAAY,CACd,IAAI,EACF,EACA,EACA,EACA,EAAO,EAAE,CACT,EAAK,CAAC,EACN,EAAK,CAAC,EACR,GAAI,CACF,GAAI,GAAM,EAAK,EAAG,KAAK,EAAI,EAAE,KAAY,IAAN,EAAS,CAC1C,GAAI,OAAO,EAAG,GAAK,EAAI,OACvB,EAAK,CAAC,OACD,KAAO,EAAE,GAAM,EAAK,EAAG,KAAK,EAAG,EAAE,QAAU,EAAK,KAAK,EAAG,MAAM,CAAE,EAAK,SAAW,GAAI,EAAK,CAAC,UAC1F,EAAK,CACZ,EAAK,CAAC,EAAG,EAAK,SACN,CACR,GAAI,CACF,GAAI,CAAC,GAAc,EAAG,QAAX,OAAsB,EAAK,EAAG,QAAQ,CAAE,OAAO,EAAG,GAAK,GAAK,cAC/D,CACR,GAAI,EAAI,MAAM,GAGlB,OAAO,GAGX,SAAS,EAAe,EAAK,EAAG,CAC9B,OAAO,EAAgB,EAAI,EAAI,EAAsB,EAAK,EAAE,EAAI,EAA4B,EAAK,EAAE,EAAI,IAAkB,CAE3H,SAAS,EAAgB,EAAK,CAC5B,GAAI,MAAM,QAAQ,EAAI,CAAE,OAAO,EAEjC,SAAS,EAA4B,EAAG,EAAQ,CACzC,KACL,IAAI,OAAO,GAAM,SAAU,OAAO,EAAkB,EAAG,EAAO,CAC9D,IAAI,EAAI,OAAO,UAAU,SAAS,KAAK,EAAE,CAAC,MAAM,EAAG,GAAG,CAEtD,GADI,IAAM,UAAY,EAAE,cAAa,EAAI,EAAE,YAAY,MACnD,IAAM,OAAS,IAAM,MAAO,OAAO,MAAM,KAAK,EAAE,CACpD,GAAI,IAAM,aAAe,2CAA2C,KAAK,EAAE,CAAE,OAAO,EAAkB,EAAG,EAAO,EAElH,SAAS,EAAkB,EAAK,EAAK,EAC/B,GAAO,MAAQ,EAAM,EAAI,UAAQ,EAAM,EAAI,QAC/C,IAAK,IAAI,EAAI,EAAG,EAAW,MAAM,EAAI,CAAE,EAAI,EAAK,IAAK,EAAK,GAAK,EAAI,GACnE,OAAO,EAET,SAAS,IAAmB,CAC1B,MAAU,UAAU;oFAA4I,CAGlK,IAAI,EAAQ,CACV,OAAQ,SACR,IAAK,SACL,KAAM,UACP,CACG,EAAgB,EAAE,CAClB,EAAe,SAAsB,EAAU,EAAU,EAAM,CACjE,MAAO,CACK,WACA,WACJ,OACP,EAGH,EAAQ,CADW,CAAC,QAAS,UAAU,CAAE,CAAC,QAAS,YAAY,CAC5C,CAAE,SAAU,EAAM,CACnC,IAAI,EAAQ,EAAe,EAAM,EAAE,CACjC,EAAO,EAAM,GACb,EAAW,EAAM,GACnB,EAAc,GAAQ,EAAa,EAAM,GAAY,EAAM,EAAM,OAAO,EACxE,CAEF,EAAQ,CADY,YAAa,YAAa,SAAU,UAAW,WAAY,QAAS,WACpE,CAAE,SAAU,EAAM,CACpC,EAAc,GAAQ,EAAa,EAAM,EAAM,EAAM,KAAK,EAC1D,CAEF,EAAQ,CADY,CAAC,WAAY,WAAW,CACxB,CAAE,SAAU,EAAO,CACrC,IAAI,EAAQ,EAAe,EAAO,EAAE,CAClC,EAAO,EAAM,GACb,EAAW,EAAM,GACnB,EAAc,GAAQ,EAAa,EAAM,EAAU,EAAM,IAAI,EAC7D,CACF,IAAI,EAAY,CACd,MAAO,CACL,OAAQ,SACR,SAAU,+BACX,CACD,IAAK,CACH,OAAQ,OACR,SAAU,uCACX,CACF,CAEG,EAAc,SAAqB,EAAS,EAAM,EAAM,EAAO,CACjE,OAAQ,EAAR,CACE,KAAK,EAAM,OAEH,IAAS,EAAc,MAAM,SAC3B,IAAU,KACZ,EAAQ,MAAM,GAAQ,GAEtB,EAAQ,MAAM,GAAQ,EAEf,IAAU,KACnB,EAAQ,GAAQ,GAEhB,EAAQ,GAAQ,EAElB,MAEJ,KAAK,EAAM,IAEP,GAAI,IAAU,KAAM,CAClB,IAAI,EAAO,EAAK,aAAa,CAC7B,EAAQ,gBAAgB,EAAK,SACpB,IAAU,IACnB,EAAQ,GAAQ,UACP,IAAU,KACnB,EAAQ,GAAQ,OACX,CACL,IAAI,EAAS,SAAS,EAAO,GAAG,CAC3B,MAAM,EAAO,GAChB,EAAQ,GAAQ,GAGpB,MAEJ,KAAK,EAAM,KAEH,CAAC,GAAI,OAAO,CAAC,QAAQ,EAAM,CAAG,EAChC,EAAQ,GAAQ,GAEhB,EAAQ,GAAQ,GAElB,QAKJ,GAAmB,SAA0B,EAAO,EAAY,CAClE,EAAQ,EAAY,SAAU,EAAU,CACtC,GAAI,EAAY,EAAe,EAAS,CAAE,CACxC,IAAI,EAAiB,EAAc,GACnC,EAAY,EAAM,KAAM,EAAe,KAAM,EAAe,SAAU,KAAK,MAEvE,KAAY,EAAM,MACpB,EAAY,EAAM,KAAM,EAAM,OAAQ,EAAU,KAAK,CAEvD,EAAM,KAAK,gBAAgB,EAAS,CAEtC,OAAO,EAAM,WAAW,IACxB,EAEA,GAAgB,SAAuB,EAAO,EAAY,CAC5D,IAAK,IAAI,KAAY,EAAY,CAC/B,IAAI,EAAQ,EAAW,GAEvB,GADA,EAAM,WAAW,GAAY,EACzB,EAAY,EAAe,EAAS,CAAE,CACxC,IAAI,EAAiB,EAAc,GACnC,EAAY,EAAM,KAAM,EAAe,KAAM,EAAe,SAAU,EAAM,CAC5E,SAEF,GAAI,EAAS,WAAW,EAAU,MAAM,OAAO,CAAE,CAC/C,EAAM,KAAK,eAAe,EAAU,MAAM,SAAU,EAAU,EAAM,CACpE,SAEF,GAAI,EAAS,WAAW,EAAU,IAAI,OAAO,CAAE,CAC7C,EAAM,KAAK,eAAe,EAAU,IAAI,SAAU,EAAU,EAAM,CAClE,SAEE,KAAY,EAAM,MACpB,EAAY,EAAM,KAAM,EAAM,OAAQ,EAAU,EAAM,CAExD,EAAM,KAAK,aAAa,EAAU,GAAS,GAAG,GAG9C,GAAoB,SAA2B,EAAS,EAAY,CACtE,IAAK,IAAI,KAAY,EAAe,CAElC,IAAI,EADiB,EAAc,GACL,SAC1B,EAAY,EAAQ,aAAa,EAAS,CAC1C,IAAa,EAAc,MAAM,SACnC,EAAW,GAAY,EAAQ,MAAM,GAC5B,OAAO,GAAc,WAC9B,EAAW,GAAY,KAIzB,GAAoB,SAA2B,EAAS,CAC1D,OAAO,MAAM,UAAU,OAAO,KAAK,EAAQ,WAAY,SAAU,EAAY,EAAU,CAIrF,OAHK,EAAY,EAAe,EAAS,KAAK,GAC5C,EAAW,EAAS,MAAQ,EAAS,OAEhC,GACN,EAAE,CAAC,EAEJ,GAAgB,SAAuB,EAAS,CAClD,IAAI,EAAa,GAAkB,EAAQ,CAE3C,OADA,GAAkB,EAAS,EAAW,CAC/B,GAEL,GAAmB,SAA0B,EAAU,EAAO,CAChE,IAAI,EAAoB,EAAE,CACtB,EAAoB,EAAE,CAC1B,IAAK,IAAI,KAAY,EAAM,WAAY,CACrC,IAAI,EAAW,EAAM,WAAW,GAC5B,EAAY,EAAS,WAAW,GAChC,IAAa,GACN,IAAc,QACvB,EAAkB,KAAK,EAAS,CAGpC,IAAK,IAAI,KAAa,EAAS,WAAY,CACzC,IAAI,EAAY,EAAM,WAAW,GAC7B,EAAa,EAAS,WAAW,GACjC,IAAc,GACP,IAAe,SACxB,EAAkB,GAAa,GAGnC,GAAiB,EAAO,EAAkB,CAC1C,GAAc,EAAO,EAAkB,EAGrC,GAAqB,WACzB,SAAS,GAAc,EAAU,EAAO,EAAO,CAC7C,IAAI,EAAyB,EAAS,SAAS,OAC3C,EAAsB,EAAM,SAAS,OACrC,MAAC,GAA0B,CAAC,GAChC,KAAI,EAAc,EAAa,EAAM,SAAS,CAC1C,EAAe,MAAM,EAAuB,CAC5C,IAAgB,IAAA,GAkBlB,EAAQ,EAAS,SAAU,SAAU,EAAe,EAAK,CACvD,IAAI,EAAa,EAAM,SAAS,GACrB,IAAe,QAIxB,EAAM,KAAK,YAAY,EAAc,KAAK,CAC1C,EAAa,GAAO,IAJpB,EAAM,EAAe,EAAW,CAChC,EAAa,GAAO,IAKtB,CA1BF,EAAQ,EAAS,SAAU,SAAU,EAAe,EAAK,CACvD,IAAI,EAAa,EAAM,KAAK,WACxB,EAAM,EAAc,WAAW,IACnC,GAAI,OAAO,UAAU,eAAe,KAAK,EAAa,EAAI,CAAE,CAC1D,IAAI,EAAa,EAAY,GACzB,MAAM,UAAU,QAAQ,KAAK,EAAY,EAAW,KAAK,GAAK,GAChE,EAAa,EAAO,EAAY,EAAW,GAAK,CAElD,EAAa,GAAO,EACpB,OAAO,EAAY,GACnB,EAAM,EAAe,EAAa,GAAK,MAEvC,EAAa,EAAO,EAAe,EAAW,GAAK,CACnD,EAAa,GAAO,GAEtB,CAaJ,EAAM,SAAW,EACjB,IAAI,EAAmB,EAAM,KAAK,WAAW,OACzC,EAAQ,EAAmB,EAC/B,GAAI,EAAQ,EACV,KAAO,EAAQ,GACb,EAAM,KAAK,YAAY,EAAM,KAAK,WAAW,EAAmB,GAAG,CACnE,IACA,KAKN,IAAI,GAAQ,SAAS,EAAM,EAAU,EAAO,EAAU,CAChD,MAAC,GAAY,CAAC,GAClB,KAAuB,EAAM,KAAK,WAClC,IAAI,EAAiB,EAAS,SAAW,EAAS,UAAY,EAAM,QACpE,GAAI,EAAS,OAAS,EAAM,MAAQ,EAElC,OADA,EAAS,aAAa,EAAS,KAAM,EAAM,KAAK,CACzC,EAAY,EAAU,EAAM,CAErC,GAAiB,EAAU,EAAM,CACjC,GAAc,EAAU,EAAO,EAAM,GAKnC,EAAS,SAAS,EAAO,EAAM,CACjC,IAAI,EAAe,UAAU,OAAS,GAAK,UAAU,KAAO,IAAA,GAAY,UAAU,GAAK,GACnF,OAAO,GAAS,WAClB,EAAO,EAAO,EAAK,EAErB,IAAI,EAAS,EAAK,UAAY,OAC1B,EAAa,EAAK,WAClB,EAAgB,EAAa,EAAW,OAAS,EACrD,GAAI,EACF,IAAI,EAAgB,EAClB,MAAU,MAAM,oEAAoE,IAC3E,IAAkB,EAC3B,MAAU,MAAM,+DAA+D,CAE/E,OAAO,EAAO,EAAW,GAAG,CAGhC,IAAI,EAAO,EAAK,WAAa,EAAI,OAAS,EAAK,WAAa,EAAI,UAAY,EAAK,QAAQ,aAAa,CAClG,EAAQ,GAAgB,IAAS,MACjC,EAAa,EAAK,WAAa,EAAI,GAAc,EAAK,CAAG,EAAE,CAC3D,EAAU,EAAgB,EAAI,KAAO,EAAK,YAC1C,EAAW,MAAM,EAAc,CAInC,OAHA,EAAQ,EAAY,SAAU,EAAO,EAAK,CACxC,EAAS,GAAO,EAAO,EAAO,EAAM,EACpC,CACK,CACC,OACM,aACF,WACD,UACH,OACN,aAAc,EACf,EClXH,SAAgB,GAAmB,EAAiC,EAAiB,CACnF,IAAI,EAAS,EAAE,CACf,IAAK,IAAI,KAAQ,EAAO,CACtB,IAAI,EAAI,EAAK,EAAK,CAClB,GAAI,EAAG,EAAO,KAAK,EAAE,MAChB,MAGP,OAAO,EAGT,SAAgB,GAAgB,EAAiC,EAAiB,CAChF,IAAI,EAAQ,EACZ,KAAO,EAAQ,EAAM,QAAU,EAAK,EAAM,GAAO,EAC/C,IAEF,OAAO,EAAM,MAAM,EAAM,CCN3B,SAAgB,GAAY,EAAsB,CAChD,OAAO,EAAK,IAAK,GAAM,EAAE,IAAM,KAAO,EAAE,MAAM,CAAC,KAAK;EAAK,CAa3D,SAAgB,EAAW,EAAwB,CACjD,MAAO,CACL,QAAS,EAAK,OAAQ,GAAM,EAAE,KAAO,SAAS,CAAC,IAAK,GAAM,EAAE,MAAM,CAElE,MAAO,EAAU,QAAS,EAAK,CAC/B,MAAO,EAAU,QAAS,EAAK,CAC/B,UAAW,EAAU,YAAa,EAAK,CACvC,OAAQ,EAAc,QAAS,EAAK,CAAC,IAAI,EAAiB,CAC1D,QAAS,EAAc,UAAW,EAAK,CAAC,IAAI,EAAY,CACzD,CAKH,SAAgB,EAAc,EAAyB,CAErD,OAAO,EADK,EAAW,EAAM,MAAM,CAAC,MAAM;EAAK,CAC7B,CAAM,CAG1B,SAAgB,EAAU,EAAa,EAAmC,CACxE,OAAO,EAAM,KAAM,GAAM,EAAE,KAAO,EAAI,EAAE,MAG1C,SAAgB,EAAc,EAAa,EAAyB,CAClE,OAAO,EAAM,OAAQ,GAAM,EAAE,KAAO,EAAI,CAAC,IAAK,GAAM,EAAE,MAAM,CAqB9D,SAAgB,EAAW,EAAyB,CAClD,OAAO,GAAa,GAAW,EAAM,CAGvC,SAAgB,GAAU,EAAgC,CACxD,IAAI,EAAQ,EAAK,MAAM,gBAAgB,CACvC,GAAI,EACF,MAAO,CACL,IAAK,EAAM,GACX,MAAO,EAAM,GACd,CAIL,SAAgB,EAAiB,EAA4B,CAC3D,GAAI,CAAC,EAAM,GAAQ,EAAiB,EAAM,CAC1C,MAAO,CACL,OACA,OAAQ,KAAK,MAAM,EAAK,CACzB,CAGH,SAAgB,EAAY,EAAiC,CAC3D,GAAI,CAAC,EAAQ,GAAU,EAAiB,EAAM,CAC9C,MAAO,CAAC,EAAQ,EAAO,CAGzB,SAAS,EAAiB,EAAiC,CACzD,IAAI,EAAK,EAAM,QAAQ,IAAI,CAC3B,GAAI,IAAO,GAAI,CACb,IAAI,EAAU,MAAM,iCAAiC,CAErD,KADA,GAAI,QAAU,EACR,EAER,MAAO,CAAC,EAAM,MAAM,EAAG,EAAG,CAAE,EAAM,MAAM,EAAK,EAAE,CAAC,CChFlD,SAAgB,GACd,EACA,EACA,EACA,EACA,EACe,CACf,IAAI,EAAe,CACjB,CAAE,IAAK,SAAU,MAAO,UAAU,SAAS,OAAO,CAAE,CACpD,CAAE,IAAK,QAAS,MAAO,OAAO,SAAS,OAAQ,CAChD,CAEG,EACA,EAOJ,OANI,aAAgB,SAClB,EAAO,EACE,IACT,EAAQ,GAGH,CAAE,OAAQ,EAAI,SAAQ,QAAO,UAAW,EAAO,OAAM,OAAM,QAAO,CAG3E,SAAgB,GAAoB,EAA4B,CAC9D,IAAI,EAAS,CAAC,WAAY,WAAa,EAAI,OAAQ,WAAa,EAAI,OAAO,CAQ3E,OANI,EAAI,OACN,EAAO,KAAK,UAAY,EAAI,MAAM,CAGpC,EAAO,KAAK,cAAgB,EAAI,UAAU,CAEnC,CAAC,EAAO,KAAK;EAAK,CAAE,GAAoB,EAAI,KAAK,CAAC,CAAC,KAAK;EAAK,CAAG,GAAY,EAAI,MAAM,CAI/F,SAAgB,GAAY,EAA4B,CAEtD,OADK,EACE;;EAAS,EADG,GAIrB,IAAI,GAA6B,EAOjC,SAAgB,IAAsB,CAEpC,MAAO,CAAE,UAAA,EADS,GACE,YAAa,GAAO,CC7D1C,SAAgB,GAAsB,EAA6B,CAEjE,OAAO,IADY,WACZ,CAAO,gBAAgB,EAAK,YAAY,CAGjD,SAAgB,GAAe,EAA2B,CACxD,IAAM,EAAM,EAAI,cAAgC,QAAQ,CAGxD,MAAO,CACI,QAHK,EAAI,cAA2B,MAGpC,CACJ,MACN,CA0CH,SAAgB,GAAc,EAAe,EAA0B,CACrE,IAAI,EAAS,EAAY,EAAM,EAAO,SAAS,CAC3C,EAAS,EAAY,EAAM,EAAO,SAAS,CAE/C,MAAO,CACL,KAAM,WACN,UAHc,SAAS,EAAY,EAAM,EAAO,YAAY,CAAE,EAG9D,CACA,aAAc,IAAA,GACd,SACA,SACA,KAAM,EAAW,EAAM,CACvB,KAAM,EAAK,KAAK;EAAK,CACtB,CAcH,SAAgB,EAAY,EAAiB,EAAe,EAAqB,CAC/E,IAAI,EAAM,EAAU,EAAK,EAAM,CAC/B,GAAI,CAAC,EAAK,MAAM,IAAI,EAAc,8BAAgC,EAAK,EAAM,KAAK;EAAK,CAAC,CACxF,OAAO,EAmBT,IAAa,EAAb,cAAmC,KAAM,CACvC,YAAY,EAAqB,EAAc,CAC7C,MAAM,EAAc;EAAO,EAAK,CAChC,KAAK,KAAO,kBCtGV,EAAiB,GADN,OAAO,SAAS,WAAa,SAAW,OAAS,MAC/B,IAAI,OAAO,SAAS,OAAO,OAAO,SAAS,WAUjE,EAAb,KAA8B,CAC5B,OAEA,iBAA4B,GAC5B,YAAuB,GACvB,eAAyB,EACzB,MAAyB,EAAE,CAC3B,OAEA,YAAY,EAAO,EAAgB,CACjC,KAAK,OAAS,IAAI,YAClB,IAAM,EAAO,IAAI,UAAU,EAAK,CAChC,KAAK,OAAS,EAGhB,QAAQ,EAAO,EAAgB,EAAe,GAAO,CACnD,IAAM,EAAO,EAAe,IAAI,UAAU,EAAK,CAAG,KAAK,OACvD,KAAK,OAAS,EAEd,SAAS,EAAe,EAAW,CACjC,QAAQ,MAAM,gBAAiB,EAAG,CAGpC,SAAS,EAAc,EAAW,CAChC,QAAQ,MAAM,eAAgB,EAAG,CAInC,EAAK,iBAAiB,QAAS,EAAe,CAE9C,EAAK,iBAAiB,OAAS,GAAW,CACxC,QAAQ,IAAI,sBAAsB,CAE9B,KAAK,kBACP,SAAS,cAAc,IAAI,MAAM,uBAAuB,CAAC,CAG3D,KAAK,YAAc,GACnB,KAAK,iBAAmB,GACxB,KAAK,eAAiB,IACtB,EAAK,oBAAoB,QAAS,EAAe,CACjD,EAAK,iBAAiB,QAAS,EAAc,CAE7C,SAAS,cAAc,IAAI,MAAM,qBAAqB,CAAC,CAEvD,KAAK,UAAU,EACf,CAEF,EAAK,iBAAiB,QAAU,GAAM,CACpC,QAAQ,IAAI,eAAe,CACvB,KAAK,aACP,SAAS,cAAc,IAAI,MAAM,wBAAwB,CAAC,CAG5D,KAAK,YAAc,GACnB,EAAK,oBAAoB,QAAS,EAAc,CAG5C,KAAK,mBACP,QAAQ,IAAI,mBAAqB,KAAK,eAAiB,IAAO,IAAI,CAClE,eAAiB,KAAK,QAAQ,EAAM,GAAK,CAAE,KAAK,eAAe,EAGjE,EAAK,oBAAoB,QAAS,EAAc,EAChD,CAEF,EAAK,iBAAiB,UAAY,GAAO,KAAK,UAAU,EAAG,CAAC,CAG9D,MAAM,WAAW,EAAuB,CACtC,GAAI,KAAK,YAAa,CACpB,IAAI,EAAM,GAAoB,EAAO,CACrC,KAAK,OAAO,KAAK,EAAI,MAErB,KAAK,MAAM,KAAK,EAAO,CAI3B,UAAmB,CAEjB,IAAI,EAAkC,KAAK,MAAM,KAAK,CAClD,IACF,QAAQ,IAAI,aAAc,EAAK,CAC1B,KAAK,WAAW,EAAK,CAC1B,KAAK,UAAU,EAKnB,UAAkB,EAAqB,CACrC,GAAI,CAAE,UAAS,QAAO,QAAS,EAAa,EAAM,KAAK,CAGnD,EAAY,SAAS,EAAY,YAAY,CAAE,EAAE,CAErD,SAAS,EAAY,EAAqB,CACxC,IAAI,EAAM,EAAU,EAAK,EAAM,CAC/B,GAAI,CAAC,EAAK,MAAM,IAAI,EAAc,8BAAgC,EAAK,EAAM,KAAK,CAClF,OAAO,EAGT,SAAS,EAAc,EAAwB,CAG7C,MAAO,CACL,YACA,aAAc,IAAA,GACd,OALW,EAAY,SAKvB,CACA,OALW,EAAY,SAKvB,CACA,KAAM,EAAmB,EAAM,CAC/B,KAAM,EAAK,KAAK;EAAK,CACtB,CAGH,SAAS,EAAY,EAAwB,CAC3C,IAAI,EAAK,EAAc,EAAK,CAG5B,MADA,GAAG,aAAe,EAAU,eAAgB,EAAM,CAC3C,EAGT,SAAS,EAAc,EAA0B,CAC/C,IAAI,EAAM,EAAK,GACf,MAAO,CACL,YACA,KAAM,EAAmB,EAAM,CAC/B,MACD,CAGH,SAAS,EAAa,EAAyB,CAC7C,GAAI,CAAE,YAAW,OAAM,SAAQ,UAAW,EAAc,EAAK,CACzD,CAAC,EAAc,GAAgB,EAAoB,EAAY,UAAU,CAAC,CAC9E,MAAO,CAAE,YAAW,OAAM,SAAQ,SAAQ,eAAc,eAAc,CAGxE,SAAS,EAAW,EAAyB,CAC3C,GAAI,CAAE,YAAW,OAAM,SAAQ,UAAW,EAAc,EAAK,CAE7D,MAAO,CAAE,YAAW,OAAM,SAAQ,SAAQ,MAD9B,EAAyB,EAAY,QAAQ,CACf,CAAO,CAGnD,OAAQ,EAAR,CACE,IAAK,WACH,KAAK,cAAc,IAAI,YAAoB,SAAU,CAAE,OAAQ,EAAY,EAAK,CAAE,CAAC,CAAC,CACpF,OAEF,IAAK,aACH,KAAK,cAAc,IAAI,YAAoB,WAAY,CAAE,OAAQ,EAAc,EAAK,CAAE,CAAC,CAAC,CACxF,OAEF,IAAK,aACH,OAAO,KAAK,cAAc,IAAI,YAAY,WAAY,CAAE,OAAQ,EAAc,EAAK,CAAE,CAAC,CAAC,CAEzF,IAAK,YACH,OAAO,KAAK,cAAc,IAAI,YAAY,UAAW,CAAE,OAAQ,EAAa,EAAK,CAAE,CAAC,CAAC,CAEvF,IAAK,UACH,OAAO,KAAK,cAAc,IAAI,YAAY,QAAS,CAAE,OAAQ,EAAW,EAAK,CAAE,CAAC,CAAC,CAEnF,QACE,MAAM,IAAI,EAAc,2BAA6B,EAAS,EAAM,KAAK,EAgD/E,iBACE,EACA,EACA,CACA,KAAK,OAAO,iBACV,EAEA,EACD,CAGH,cAAwD,EAAgC,CACtF,KAAK,OAAO,cAAc,EAAE,CAG9B,YAAa,CACX,KAAK,YAAc,GACnB,KAAK,iBAAmB,GACxB,KAAK,OAAO,OAAO,GA4CvB,SAAgB,EAAa,EAA2B,CACtD,IAAI,EAAQ,EAAI,MAAM;EAAK,CACvB,EAAkB,EAAM,GACxB,EAAgB,EAAmB,EAAM,MAAM,EAAE,CAAC,CAMtD,MAAO,CAAE,UAAS,QAAO,KAFd,GAAW,GAAM,GAAK,GAAI,EAAM,MAAM,EAAM,OAAS,EAAE,CAEzC,CAAM,CCjTjC,SAAwB,EAAS,EAAW,EAAO,IAAK,EAAU,EAAE,CAAE,CACrE,GAAI,OAAO,GAAc,WACxB,MAAU,UAAU,wDAAwD,OAAO,EAAU,KAAK,CAGnG,GAAI,EAAO,EACV,MAAU,WAAW,+BAA+B,CAGrD,GAAI,OAAO,GAAY,UACtB,MAAU,UAAU,6FAA6F,CAGlH,GAAM,CAAC,aAAa,EAEhB,EACA,EACA,EACA,EACA,EAEJ,SAAS,GAAM,CACd,IAAM,EAAc,EACd,EAAgB,EAItB,MAHA,GAAgB,IAAA,GAChB,EAAkB,IAAA,GAClB,EAAS,EAAU,MAAM,EAAa,EAAc,CAC7C,EAGR,SAAS,GAAQ,CAChB,IAAM,EAAO,KAAK,KAAK,CAAG,EAEtB,EAAO,GAAQ,GAAQ,EAC1B,EAAY,WAAW,EAAO,EAAO,EAAK,EAE1C,EAAY,IAAA,GAEP,IACJ,EAAS,GAAK,GAKjB,IAAM,EAAY,SAAU,GAAG,EAAY,CAC1C,GACC,GACG,OAAS,GACT,OAAO,eAAe,KAAK,GAAK,OAAO,eAAe,EAAc,CAEvE,MAAU,MAAM,yEAAyE,CAG1F,EAAgB,KAChB,EAAkB,EAClB,EAAY,KAAK,KAAK,CAEtB,IAAM,EAAU,GAAa,CAAC,EAM9B,GAJA,AACC,IAAY,WAAW,EAAO,EAAK,CAGhC,EAEH,MADA,GAAS,GAAK,CACP,GAqCT,OA/BA,OAAO,eAAe,EAAW,YAAa,CAC7C,KAAM,CACL,OAAO,IAAc,IAAA,IAEtB,CAAC,CAEF,EAAU,UAAc,CAClB,IAIL,aAAa,EAAU,CACvB,EAAY,IAAA,GACZ,EAAgB,IAAA,GAChB,EAAkB,IAAA,KAGnB,EAAU,UAAc,CAClB,GAIL,EAAU,SAAS,EAGpB,EAAU,YAAgB,CACzB,EAAS,GAAK,CAEd,EAAU,OAAO,EAGX,EC5FR,IAAa,EAAe,GACnB,GAAK,YAAc,IAAA,GAK5B,SAAgB,EAAgB,EAAyB,CACvD,IAAI,EAAQ,IAAI,MAAM,cAAe,CAAE,QAAS,GAAM,CAAC,CACvD,EAAK,cAAc,EAAM,CAG3B,SAAgB,EACd,EACA,EACM,CAEN,EAAK,iBAA4B,OAAO,CAAC,QAAS,GAAY,CAC5D,EAAQ,UAAY,SAAU,EAAgB,EAAkB,CAC9D,OAAO,EAAU,EAAS,EAAQ,EAAK,EAGzC,EAAQ,YAAc,EAAQ,QAAQ,aAAe,OAErD,EAAQ,oBAAsB,UAAY,CACpC,EAAQ,eAAiB,CAAC,EAAQ,eAAe,cACnD,EAAQ,cAAc,YAAc,KAIxC,EAAgB,EAAK,EACrB,CClCJ,SAAgB,GAAc,EAAuD,CACnF,EAAe,UAAW,EAAG,CAG/B,SAAgB,GAAY,EAAuD,CACjF,EAAe,QAAS,EAAG,CAG7B,SAAgB,EACd,EACA,EACM,CACN,SAAS,iBAAiB,EAAO,SAAU,EAAkB,CAC3D,GAAI,EAAE,EAAE,kBAAkB,aAAc,CACtC,QAAQ,KAAK,uEAAwE,EAAE,CACvF,OAEF,IAAI,EAAS,EAAE,OAEX,EAAa,KAAO,EAAQ,EAAE,IAC9B,EAAS,EAAO,QAAQ,GAC5B,GAAI,CAAC,EAAQ,OAEb,EAAE,gBAAgB,CAClB,IAAM,EAAS,EAAuB,EAAO,CAC7C,GAAI,CAAC,EAAQ,CACX,QAAQ,MAAM,mBAAoB,EAAO,CACzC,OAEF,EAAG,EAAQ,EAAO,EAClB,CAGJ,SAAgB,EACd,EACA,EACM,CACN,SAAS,iBAAiB,EAAO,SAAU,EAAG,CAC5C,GAAI,EAAE,EAAE,kBAAkB,SAAU,CAClC,QAAQ,KAAK,2EAA4E,EAAE,CAC3F,OAKF,IAAI,EAHK,EAAE,OAGK,QAAqB,WAAa,EAAQ,IAAI,CAC9D,GAAI,CAAC,EAAQ,OAEb,EAAE,gBAAgB,CAClB,IAAI,EAAS,EAAuB,EAAO,CAC3C,GAAI,CAAC,EAAQ,CACX,QAAQ,MAAM,mBAAoB,EAAO,CACzC,OAEF,IAAM,EAAS,EAAO,QAAQ,KAAO,GACrC,GAAI,IAAW,IAAA,GAAW,CACxB,QAAQ,MAAM,mBAAoB,EAAQ,EAAM,CAChD,OAEF,EAAG,EAAQ,EAAO,EAClB,CAGJ,SAAgB,GAAY,EAAuD,CACjF,EAAoB,QAAS,EAAG,CAGlC,SAAgB,GAAe,EAAuD,CACpF,EAAoB,WAAY,EAAG,CAGrC,SAAgB,GAAe,EAAuD,CACpF,SAAS,iBAAiB,WAAY,SAAU,EAAgB,CAC9D,IAAI,EAAS,EAAE,OAAO,OAClB,EAAS,EAAE,OAAO,OACtB,EAAG,EAAQ,EAAO,EAClB,CAEF,SAAS,iBAAiB,iBAAkB,SAAU,EAAgB,CACpE,IAAI,EAAS,EAAE,OAAO,aAClB,EAAS,EAAE,OAAO,OACtB,EAAG,EAAQ,EAAO,EAClB,CAEF,SAAS,iBAAiB,iBAAkB,SAAU,EAAgB,CACpE,IAAI,EAAS,EAAE,OAAO,aAClB,EAAS,EAAE,OAAO,OACtB,EAAG,EAAQ,EAAO,EAClB,CAGJ,SAAgB,EAAW,EAAyB,CAElD,EAAK,iBAA8B,gBAAgB,CAAC,QAAS,GAAS,CACpE,IAAI,EAAQ,SAAS,EAAK,QAAQ,OAAS,GAAG,EAAI,EAC9C,EAAS,EAAK,QAAQ,OAK1B,eAAiB,CACf,IAAI,EAAS,EAAuB,EAAK,CAGzC,GAAI,EAAK,QAAQ,QAAU,EAEzB,OAGF,IAAM,EAAQ,IAAI,YAAY,WAAY,CAAE,QAAS,GAAM,OAAQ,CAAE,SAAQ,SAAQ,CAAE,CAAC,CACxF,EAAK,cAAc,EAAM,EACxB,EAAM,EACT,CAGJ,SAAgB,EAAiB,EAAyB,CACxD,EAAK,iBAA8B,sBAAsB,CAAC,QAAS,GAAS,CAC1E,IAAI,EAAe,EAAK,QAAQ,aAE5B,EAAS,EAAiB,EAAK,CAEnC,EAAK,iBAAqB,CACxB,IAAM,EAAQ,IAAI,YAAY,iBAAkB,CAC9C,QAAS,GACT,OAAQ,CAAE,SAAQ,eAAc,CACjC,CAAC,CACF,EAAK,cAAc,EAAM,GAE3B,CAGJ,SAAgB,EAAiB,EAAyB,CACxD,EAAK,iBAA8B,sBAAsB,CAAC,QAAS,GAAS,CAC1E,IAAI,EAAe,EAAK,QAAQ,aAE5B,EAAS,EAAiB,EAAK,CAEnC,EAAK,iBAAqB,CACxB,IAAM,EAAQ,IAAI,YAAY,iBAAkB,CAC9C,QAAS,GACT,OAAQ,CAAE,SAAQ,eAAc,CACjC,CAAC,CACF,EAAK,cAAc,EAAM,GAE3B,CAGJ,SAAgB,GAAa,EAAsE,CACjG,SAAS,iBAAiB,SAAU,SAAU,EAAG,CAC/C,GAAI,EAAE,EAAE,kBAAkB,aAAc,CACtC,QAAQ,KAAK,wEAAyE,EAAE,CACxF,OAIF,IAAI,EAFK,EAAE,OAEK,QAA0B,kBAAkB,CAE5D,GAAI,CAAC,EAAQ,OAGb,GAFA,EAAE,gBAAgB,CAEd,EAAO,QAAU,KAAM,CACzB,QAAQ,MAAM,uBAAwB,EAAO,CAC7C,OAGF,IAAI,EAAS,EAAuB,EAAO,CAC3C,GAAI,CAAC,EAAQ,CACX,QAAQ,MAAM,+BAA+B,CAC7C,OAGF,GAAI,CAAC,EAAO,QAAQ,SAAU,CAC5B,QAAQ,MAAM,qBAAsB,EAAO,CAC3C,OAGF,EAAG,EAAQ,EAAO,QAAQ,SAAU,EAAO,MAAM,EACjD,CAOJ,SAAgB,GACd,EACA,EACM,CACN,SAAS,iBAAiB,QAAS,SAAU,EAAG,CAC9C,GAAI,EAAE,EAAE,kBAAkB,aAAc,CACtC,QAAQ,KAAK,uEAAwE,EAAE,CACvF,OAGF,IAAM,EADG,EAAE,OACO,QAA0B,iBAAiB,CAE7D,GAAI,CAAC,EAAQ,OAEb,IAAI,EAAQ,SAAS,EAAO,QAAQ,OAAS,GAAG,EAAI,IAChD,EAAQ,KACV,QAAQ,KAAK,oDAAoD,CAGnE,EAAE,gBAAgB,CAElB,IAAM,EAAS,EAAuB,EAAO,CAC7C,GAAI,CAAC,EAAQ,CACX,QAAQ,MAAM,mBAAoB,EAAO,CACzC,OAIF,EAAc,EAAO,CAErB,AACE,EAAO,oBAAoB,MAAe,CACxC,GAAI,CAAC,EAAO,QAAQ,QAAS,CAC3B,QAAQ,MAAM,oBAAqB,EAAO,CAC1C,OAGF,EAAG,EAAQ,EAAO,QAAQ,QAAS,EAAO,MAAM,EAC/C,EAAM,CAGX,EAAO,mBAAmB,EAC1B,CAGJ,SAAgB,GACd,EACM,CACN,SAAS,iBAAiB,SAAU,SAAU,EAAG,CAC/C,GAAI,EAAE,EAAE,kBAAkB,iBAAkB,CAC1C,QAAQ,KAAK,4EAA6E,EAAE,CAC5F,OAEF,IAAI,EAAO,EAAE,OAEb,GAAI,CAAC,EAAK,QAAQ,SAAU,CAC1B,QAAQ,MAAM,qBAAsB,EAAK,CACzC,OAGF,EAAE,gBAAgB,CAElB,IAAI,EAAS,EAAuB,EAAK,CACnC,EAAW,IAAI,SAAS,EAAK,CACnC,GAAI,CAAC,EAAQ,CACX,QAAQ,MAAM,mBAAoB,EAAO,CACzC,OAEF,EAAG,EAAQ,EAAK,QAAQ,SAAU,EAAS,EAC3C,CAGJ,SAAS,GAAgB,EAAuC,CAE9D,OADiB,EAAK,QAAqB,gBACpC,EAAY,QAAQ,QAAU,EAAK,QAAQ,OAAO,EAAE,GAG7D,SAAS,EAAuB,EAA0C,CACxE,IAAM,EAAS,EAAiB,EAAK,CAErC,GAAI,CAAC,EAAY,EAAO,CAAE,CACxB,QAAQ,MAAM,yBAA0B,EAAO,CAC/C,OAGF,OAAO,EAGT,SAAS,EAAiB,EAA4C,CACpE,IAAI,EAAW,GAAgB,EAAK,CAChC,EAAS,GAAY,SAAS,eAAe,EAAS,CAE1D,GAAI,CAAC,EAAQ,CACX,QAAQ,MAAM,uBAAwB,EAAU,EAAK,CACrD,OAGF,OAAO,EC/RT,SAAgB,GAAS,EAAe,CACtC,GAAI,GAAS,IAAc,CAAE,CACvB,GAAS,KAAI,EAAQ,IAAM,GAC/B,IAAI,EAAM,SAAS,SAAW,EAE9B,OAAO,QAAQ,aAAa,EAAE,CAAE,GAAI,EAAI,EAI5C,SAAS,IAAuB,CAC9B,IAAM,EAAQ,OAAO,SAAS,OAC9B,OAAO,EAAM,WAAW,IAAI,CAAG,EAAM,UAAU,EAAE,CAAG,ECNtD,eAAsB,GAAe,EAAkD,CAErF,IAAI,EAAM,OAAO,SAAS,KAEtB,EAAe,CACjB,OAAQ,YACR,aAAc,EAAI,OAClB,gBAAiB,EAAI,UACrB,aAAc,EAAI,OAClB,YAAa,EAAI,OAAS,KAC3B,CAED,QAAQ,IAAI,iBAAkB,EAAI,KAAK,CAiBvC,GAAM,CAAE,QAAO,QAAS,GAAc,MAPrB,MARD,MAAM,EAAK,CACzB,OAAQ,OACC,UACT,KAAM,EAAI,KAEV,SAAU,SACX,CAAC,EAEmB,MAAM,CAOgB,CAE3C,OADiB,GAAc,EAAO,EAC/B,CAGT,SAAgB,GAAc,EAA6B,CACzD,IAAI,EAAQ,EAAK,MAAM;EAAK,CAK5B,MAAO,CAAE,MAHG,EAAW,EAAM,MAAM,EAAE,CAG5B,CAAO,KAFL,EAAM,MAAM,EAEP,CAAM,CCRxB,QAAQ,IAAI,mBAAiC,CAE7C,IAAI,EACA,EAAkB,IAAI,IAG1B,eAAe,EAAU,EAAmB,EAAgB,EAAmB,CAC7E,GAAI,EAAO,eAAiB,CAAC,EAAO,eAAe,aAE7C,EAAO,aAAe,OAAQ,CAChC,QAAQ,KAAK,8CAA+C,EAAO,cAAe,EAAO,CACzF,OAIJ,EAAO,SAAW,OAAO,eAAiB,CAGxC,EAAO,UAAU,IAAI,cAAc,EAClC,IAAI,CAEP,IAAI,EAAQ,EAAO,QAAQ,MAEvB,EAAM,IAAY,CAClB,EAAM,GAAc,EAAO,GAAI,EAAQ,EAAO,EAAI,UAAW,EAAK,CAKtE,GAFA,EAAO,cAAgB,EAEnB,CAAC,EAAI,KAGF,EAAK,WAAW,EAAI,KACpB,CACL,IAAI,EAAM,MAAM,GAAe,EAAI,CAKnC,OAJA,QAAQ,IAAI,WAAY,EAAI,CAIpB,EAAI,KAAZ,CACE,IAAK,WACH,QAAQ,IAAI,uBAAuB,CACnC,MACF,IAAK,WACH,EAAe,EAAI,CACnB,QAKR,SAAS,EAAc,EAAkB,CACvC,EAAW,EAAQ,aAAc,EAAQ,aAAa,CAGxD,SAAS,GAAY,EAAa,CAChC,IAAI,EAAS,SAAS,eAAe,EAAG,OAAO,CAC/C,EAAe,EAAG,MAAO,EAAO,CAIlC,SAAS,GAAe,EAAe,CACrC,QAAQ,IAAI,WAAY,EAAI,CAG5B,EAAa,EAAI,KAAK,SAAW,EAAE,CAAC,CAEpC,OAAO,SAAS,KAAO,EAAI,IAI7B,SAAS,EAAe,EAAe,CAErC,IAAI,EAAS,EAAU,EAAI,OAAQ,EAAI,CAClC,IAGL,OAAO,EAAO,cACd,aAAa,EAAO,SAAS,CAC7B,EAAO,UAAU,OAAO,cAAc,EAGxC,SAAS,GAAa,EAAY,CAC5B,EAAG,KAAK,MACV,EAAU,EAAG,OAAQ,EAAG,CACd,EAAG,aAGb,EAAU,EAAG,aAAc,EAAG,CAF9B,QAAQ,MAAM,8BAA+B,EAAG,CAMpD,SAAS,EAAU,EAAsB,EAA0C,CAIjF,IAAI,EAAS,SAAS,eAAe,EAAa,CAElD,GAAI,CAAC,EAAY,EAAO,CAAE,CACxB,QAAQ,MAAM,oCAAqC,EAAc,EAAI,CACrE,OAGF,GAAI,EAAO,eAAe,WAAa,EAAI,UAAY,EAAO,cAAc,UAW1E,OARA,QAAQ,KACN,wBACE,EAAI,UACJ,SACA,EAAO,cAAc,UACrB,MACA,EAAI,OACP,CACM,KACE,EAAO,eAAe,YAG/B,OAFA,QAAQ,KAAK,oBAAqB,EAAO,eAAe,UAAU,CAClE,OAAO,EAAO,cACP,EAGT,IAAI,EAAqB,GAAe,GAAsB,EAAI,KAAK,CAAC,CAExE,GAAI,CAAC,EAAO,QAEV,OADA,QAAQ,MAAM,kBAAmB,EAAI,KAAK,CACnC,EAIT,GAAO,EAAO,IAAI,CAGlB,IAAM,EAAa,EAAO,EAAO,CAC7B,EAAc,EAAO,EAAO,QAAQ,CACpC,EAAO,EAAK,WAEhB,GAAI,CAAC,EAAI,KAAK,OAAS,EAAK,IAAS,EAAO,GAAI,CAC9C,QAAQ,MAAM,iCAAkC,EAAK,GAAO,WAAY,EAAO,GAAG,CAClF,OAGF,IAAI,EAAQ,EAAK,cACjB,EAAK,WAAa,EAAI,WAEtB,GAAM,EAAM,EAAI,CAGhB,IAAI,EAAY,SAAS,eAAe,EAAO,GAAG,CAwBlD,OAtBK,GAKL,EAAgB,EAAU,CAGtB,IAAU,IAAA,IAAa,GAAS,KAAM,OAAO,EAAU,QAAQ,MAC9D,EAAU,QAAQ,MAAQ,EAG/B,EAAY,EAAI,KAAM,EAAU,CAChC,EAAa,EAAI,KAAK,SAAW,EAAE,CAAC,CAGpC,EAAW,EAAU,CACrB,EAAiB,EAAU,CAC3B,EAAiB,EAAU,CAC3B,GAAU,EAAU,CACpB,EAAiB,EAAW,EAAU,CAE/B,IArBL,QAAQ,KAAK,mBAAoB,EAAO,GAAG,CACpC,GA8BX,SAAS,EAAa,EAAmB,CACvC,EAAQ,QAAS,GAAmB,CAClC,QAAQ,IAAI,cAAe,EAAO,CAClC,SAAS,OAAS,GAClB,CAGJ,SAAS,EAAY,EAAgB,EAA4B,CAC3D,EAAK,OAAS,MAChB,GAAS,EAAK,MAAM,CAGlB,EAAK,WAAa,OACpB,SAAS,MAAQ,EAAK,WAGxB,EAAK,QAAQ,QAAS,GAAgB,CACpC,EAAe,EAAa,EAAO,EACnC,CAEF,EAAK,SAAS,SAAS,CAAC,EAAQ,KAAY,CAC1C,EAAW,EAAQ,EAAO,EAC1B,CAGJ,SAAS,EAAe,EAA0B,EAA4B,CAC5E,eAAiB,CACf,IAAI,EAAQ,IAAI,YAAY,EAAY,KAAM,CAAE,QAAS,GAAM,OAAQ,EAAY,OAAQ,CAAC,EAC1E,GAAU,UAChB,cAAc,EAAM,EAC/B,GAAG,CAGR,SAAS,EAAW,EAAgB,EAAuB,CACzD,eAAiB,CACf,IAAI,EAAO,OAAO,WAAW,UAAU,EAAO,CAC1C,GACG,EAAU,EAAM,EAAQ,IAAA,GAAU,EAExC,GAAG,CAGR,SAAS,GAAU,EAAqB,CACtC,IAAI,EAAU,EAAO,cAAgC,cAAc,CAC/D,GAAS,OACX,EAAQ,OAAO,CAGjB,EAAO,iBAAmC,eAAe,CAAC,QAAS,GAAU,CAC3E,IAAI,EAAM,EAAM,aAAa,QAAQ,CACjC,IAAQ,OACV,EAAM,MAAQ,IAEhB,CAEF,EAAO,iBAAmC,uBAAuB,CAAC,QAAS,GAAa,CAEtF,EAAS,QADK,EAAS,QAAQ,SAAW,QAE1C,CAGJ,SAAS,GAAO,EAA8B,CAC5C,GAAI,CAAC,EAAK,OACV,IAAM,EAAQ,EAAI,OAAO,SACpB,KACL,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAAK,CACrC,IAAM,EAAO,EAAM,KAAK,EAAE,CACtB,GAAQ,EAAgB,IAAI,EAAK,QAAQ,EAAI,GAAS,EAAW,QACnE,EAAW,MAAM,WAAW,EAAK,QAAQ,CACzC,EAAgB,IAAI,EAAK,QAAQ,GAKvC,SAAS,IAAO,CAId,EAFW,EAAc,SAAS,eAAe,eAAe,EAAE,WAAa,GAEnE,CAAM,KAAK,CAEvB,IAAM,EAAQ,SAAS,KAAK,cAAc,QAAQ,CAE9C,IAAU,MAGZ,QAAQ,KAAK,4CAA4C,CACzD,EAAa,SAAS,cAAc,QAAQ,CAC5C,EAAW,KAAO,WAClB,SAAS,KAAK,YAAY,EAAW,EALrC,EAAa,EAQf,GAAe,eAAgB,EAAmB,EAAgB,CAC3D,EAAU,EAAQ,EAAQ,IAAA,GAAU,EACzC,CAEF,EAAW,SAAS,KAAK,CACzB,EAAiB,SAAS,KAAK,CAC/B,EAAiB,SAAS,KAAK,CAC/B,EAAiB,SAAS,KAAM,EAAU,CAE1C,GAAY,eAAgB,EAAmB,EAAgB,CAExD,EAAU,EAAQ,EAAQ,IAAA,GAAU,EACzC,CAEF,GAAe,eAAgB,EAAmB,EAAgB,CAE3D,EAAU,EAAQ,EAAQ,IAAA,GAAU,EACzC,CAEF,GAAc,eAAgB,EAAmB,EAAgB,CAE1D,EAAU,EAAQ,EAAQ,IAAA,GAAU,EACzC,CAEF,GAAY,eAAgB,EAAmB,EAAgB,CAExD,EAAU,EAAQ,EAAO,EAC9B,CAEF,GAAiB,eAAgB,EAAmB,EAAgB,EAAgB,CAE7E,EAAU,EAAQ,EAAQ,EAAK,EACpC,CAEF,GAAa,eAAgB,EAAmB,EAAgB,EAAe,CACxE,EAAU,EAAQ,EAAQ,EAAM,EACrC,CAEF,SAAS,EAAgB,EAAmB,CACtC,EAAO,aAAe,WACxB,EAAO,qBAAqB,CAIhC,GACE,EACA,eAAgB,EAAmB,EAAgB,EAAmB,CAC/D,EAAU,EAAQ,EAAQ,EAAM,EAExC,CAGH,SAAS,iBAAiB,mBAAoB,GAAK,CAEnD,IAAM,EAAO,IAAI,EAEjB,EAAK,SAAS,CACd,EAAK,iBAAiB,SAAW,GAA4B,GAAa,EAAG,OAAO,CAAC,CACrF,EAAK,iBAAiB,WAAa,GAA8B,EAAe,EAAG,OAAO,CAAC,CAC3F,EAAK,iBAAiB,WAAa,GAA8B,GAAe,EAAG,OAAO,CAAC,CAC3F,EAAK,iBAAiB,UAAY,GAA6B,EAAc,EAAG,OAAO,CAAC,CACxF,EAAK,iBAAiB,QAAU,GAA6B,GAAY,EAAG,OAAO,CAAC,CA8CpF,OAAO,UAAY,CACN,YACI,gBACf,OAAQ,SAAU,EAAK,GAAG,EAAe,CACvC,OAAO,EAAO,QAAQ,EAAK,IAAU,EAAM,IAAM,KAAK,UAAU,EAAM,CAAE,EAAI,EAE9E,UAAW,SAAU,EAAQ,CAC3B,IAAI,EAAU,SAAS,eAAe,EAAO,CAC7C,GAAI,CAAC,EAAY,EAAQ,CAAE,CACzB,QAAQ,MAAM,cAAgB,EAAS,uBAAuB,CAC9D,OAEF,OAAO,GAET,OAAQ,EACT"}
diff --git a/client/util/live-reload.js b/client/util/live-reload.js
--- a/client/util/live-reload.js
+++ b/client/util/live-reload.js
@@ -1,4 +1,4 @@
-// This isn't magic. If you want custom behavior, copy and modify this however you like. 
+// This isn't magic. If you want custom behavior, copy and modify this however you like.
 //
 // As with any custom js, add to a single page via the `script` combinator
 //  page = do
@@ -12,14 +12,14 @@
 console.log("Live Reload enabled")
 
 function showNotification(message) {
-  const notification = document.createElement('div');
+  const notification = document.createElement("div")
   notification.classList.add("live-reload")
-  notification.innerHTML = message;
+  notification.innerHTML = message
   jackIn(notification.style)
-  notification.addEventListener('click', function() {
+  notification.addEventListener("click", function () {
     notification.remove()
   })
-  document.body.appendChild(notification);
+  document.body.appendChild(notification)
 }
 
 document.addEventListener("hyp-socket-disconnect", () => {
@@ -32,17 +32,16 @@
   }, 0)
 })
 
-
 // duplicate cyber style stuff here so the default live reload is fun
 function jackIn(style) {
-  style.position = 'fixed';
-  style.bottom = '15px';
-  style.left = '15px';
-  style.right = '15px';
-  style.backgroundColor = 'rgba(160, 63, 56, 1.0)';
-  style.color = '#fff';
-  style.borderTop = 'solid #EC6458 4px';
-  style.padding = '15px';
-  style.zIndex = '1000';
-  style.clipPath = 'polygon(0 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%, 0 100%)';
+  style.position = "fixed"
+  style.bottom = "15px"
+  style.left = "15px"
+  style.right = "15px"
+  style.backgroundColor = "rgba(160, 63, 56, 1.0)"
+  style.color = "#fff"
+  style.borderTop = "solid #EC6458 4px"
+  style.padding = "15px"
+  style.zIndex = "1000"
+  style.clipPath = "polygon(0 0, 100% 0, 100% calc(100% - 16px), calc(100% - 16px) 100%, 0 100%)"
 }
diff --git a/docs/Docgen.hs b/docs/Docgen.hs
new file mode 100644
--- /dev/null
+++ b/docs/Docgen.hs
@@ -0,0 +1,278 @@
+module Main where
+
+import Data.Char (isAlpha, isSpace)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
+import System.Directory
+import System.Environment (getArgs)
+import System.FilePath
+
+
+main :: IO ()
+main = do
+  args <- getArgs
+  case args of
+    [original, input, output] -> do
+      src <- readSource input
+      expanded <- expandFile src
+      let linePragma = T.pack $ "{-# LINE 1 \"" ++ original ++ "\" #-}"
+      let final = SourceCode [linePragma] <> expanded
+      T.writeFile output $ T.unlines final.lines
+    _ -> error "Usage (Hyperbole Internal Only): docgen src/MyModule.hs /tmp/input/MyModule.hs /build/output/Original.hs"
+
+
+-- test :: IO ()
+-- test = do
+--   src <- readSource "./src/Web/Hyperbole.hs"
+--   SourceCode lns <- expandFile src
+--   mapM_ print lns
+
+-- expandSourcesTo :: FilePath -> IO ()
+-- expandSourcesTo tmpDir = do
+--   allFiles <- relativeSourceFiles "./src"
+--   -- mapM_ (putStrLn . ("SOURCE " <>)) allFiles
+--   mapM_ (expandAndCopyFileTo tmpDir) allFiles
+
+-- copyExtraFilesTo :: FilePath -> IO ()
+-- copyExtraFilesTo tmpDir = do
+--   createDirectoryIfMissing True tmpDir
+--   copyFile "./cabal.project" (tmpDir </> "cabal.project")
+--   copyFile "./hyperbole.cabal" (tmpDir </> "hyperbole.cabal")
+--   copyFile "./README.md" (tmpDir </> "README.md")
+--   copyFile "./CHANGELOG.md" (tmpDir </> "CHANGELOG.md")
+--   copyFile "./LICENSE" (tmpDir </> "LICENSE")
+--   createDirectoryIfMissing True (tmpDir </> "client/dist")
+--   copyFile "./client/dist/hyperbole.js" (tmpDir </> "client/dist/hyperbole.js")
+--   copyFile "./client/dist/hyperbole.js.map" (tmpDir </> "client/dist/hyperbole.js.map")
+--   createDirectoryIfMissing True (tmpDir </> "client/util")
+--   copyFile "./client/util/live-reload.js" (tmpDir </> "client/util/live-reload.js")
+
+-- expandAndCopyFileTo :: FilePath -> FilePath -> IO ()
+-- expandAndCopyFileTo tmpDir pth = do
+--   putStrLn $ "EXPANDING " <> pth
+--   src <- readSource pth
+--   expanded <- expandFile src
+--   writeSource tmpDir pth expanded
+
+readSource :: FilePath -> IO SourceCode
+readSource pth = do
+  inp <- T.readFile pth
+  pure $ SourceCode $ T.lines inp
+
+
+writeSource :: FilePath -> FilePath -> SourceCode -> IO ()
+writeSource tmpDir relPath src = do
+  let pth = tmpDir </> cleanRelativeDir relPath
+  -- putStrLn $ "WRITE " <> pth <> " " <> show (length src.lines)
+  createDirectoryIfMissing True $ takeDirectory pth
+  T.writeFile pth $ T.unlines src.lines
+ where
+  cleanRelativeDir =
+    dropWhile (== '/') . dropWhile (== '.')
+
+
+-- relativeSourceFiles :: FilePath -> IO [FilePath]
+-- relativeSourceFiles dir = do
+--   contents <- tryDirectory dir
+--   let folders = filter isFolder contents
+--   let files = filter isSourceFile contents
+--
+--   files' <- mapM (relativeSourceFiles . addDir) folders
+--
+--   pure $ fmap addDir files <> mconcat files'
+--  where
+--   isSourceFile pth = takeExtension pth == ".hs"
+--   isFolder pth = takeExtension pth == ""
+--   addDir = (dir </>)
+--   tryDirectory pth = do
+--     res <- try $ listDirectory pth
+--     case res of
+--       Left (_ :: SomeException) -> do
+--         putStrLn $ "SKIPPED" <> pth
+--         pure []
+--       Right files -> pure files
+
+data Macro
+  = Embed
+  { moduleName :: ModuleName
+  , definition :: TopLevelDefinition
+  }
+  deriving (Eq)
+newtype SourceCode = SourceCode {lines :: [Text]}
+  deriving newtype (Monoid, Semigroup)
+instance Show Macro where
+  -- show (Example p) = "Example " <> show p
+  show (Embed mn def) = "Embed " <> show mn <> " " <> show def
+
+
+newtype ModuleName = ModuleName Text
+  deriving newtype (Eq, Show)
+
+
+newtype TopLevelDefinition = TopLevelDefinition Text
+  deriving newtype (Show, Eq)
+
+
+expandFile :: SourceCode -> IO SourceCode
+expandFile (SourceCode lns) =
+  SourceCode . mconcat <$> mapM expandLine lns
+
+
+-- > EMBED Example/Docs/BasicPage.hs page
+expandLine :: Text -> IO [Text]
+expandLine line = do
+  case parseMacro line of
+    Nothing -> do
+      pure [line]
+    Just (pre, Embed src def) -> do
+      expandEmbed src pre def
+ where
+  -- Just (pre, Example src) -> do
+  --   expandExample src pre
+
+  parseMacro :: Text -> Maybe (Text, Macro)
+  parseMacro inp = do
+    parseEmbed inp -- <|> parseExample inp
+
+  -- parseExample l = do
+  --   case T.splitOn "#EXAMPLE " l of
+  --     [prefix, src] -> do
+  --       pure (prefix, Example $ path src)
+  --     _ -> Nothing
+
+  parseEmbed l = do
+    case T.splitOn "#EMBED " l of
+      [prefix, info] -> do
+        (mn, definition) <- splitSrcDef $ T.dropWhile (== ' ') info
+        pure (prefix, Embed mn definition)
+      _ -> Nothing
+
+  splitSrcDef inp =
+    let (mn, def) = T.breakOn " " inp
+     in pure (ModuleName mn, TopLevelDefinition $ T.drop 1 def)
+
+
+-- look it up as a URI...
+
+-- * #EXAMPLE /simple
+
+
+-- expandExample :: Path -> Text -> IO [Text]
+-- expandExample p prefix = do
+--   let pre = if T.null prefix then "▶️ " else prefix
+--   r <- appRoute
+--   pure [pre <> "[" <> routeTitle r <> "](" <> uriToText (exampleBaseURI ./. p) <> ")"]
+--  where
+--   appRoute :: IO AppRoute
+--   appRoute = do
+--     case matchRoute @AppRoute p of
+--       Nothing -> fail $ "Could not find example: " <> cs (pathToText False p)
+--       Just r -> pure r
+
+-- exampleBaseURI :: URI
+-- exampleBaseURI = [uri|https://hyperbole.live|]
+
+modulePath :: ModuleName -> FilePath
+modulePath (ModuleName mn) = cs $ T.replace "." "/" mn <> ".hs"
+
+
+expandEmbed :: ModuleName -> Text -> TopLevelDefinition -> IO [Text]
+expandEmbed mn pfx def = do
+  let src = modulePath mn
+  -- putStrLn $ "  embed: " <> src
+  source <- T.readFile $ "./demo/" <> src
+  expanded <- requireTopLevel def (SourceCode $ T.lines source)
+  pure $ fmap markupLine expanded
+ where
+  requireTopLevel :: TopLevelDefinition -> SourceCode -> IO [Text]
+  requireTopLevel tld sc =
+    case findTopLevel tld sc of
+      [] -> fail $ "Could not find: " <> show (Embed mn def) <> " " <> show def
+      lns -> pure lns
+
+  -- addPrefix line = embed.prefix <> line
+  markupLine :: Text -> Text
+  markupLine line =
+    case pfx of
+      "" -> markupLineAt line
+      _ -> markupLinePrefix line
+  markupLineAt =
+    T.replace "\"" "\\\"" . highlightTermsLine
+  markupLinePrefix line =
+    pfx <> line
+
+
+highlightTermsLine :: Text -> Text
+highlightTermsLine ln = mconcat $ fmap highlightWord $ T.groupBy isSameTerm ln
+ where
+  isSameTerm :: Char -> Char -> Bool
+  isSameTerm c1 c2 =
+    (isAlpha c1 && isAlpha c2)
+      || (isSpace c1 && isSpace c2)
+
+  highlightWord :: Text -> Text
+  highlightWord w =
+    if w `elem` terms
+      then "'" <> w <> "'"
+      else w
+
+  terms :: [Text]
+  terms =
+    [ "HyperView"
+    , "View"
+    , "Action"
+    , "update"
+    , "hyper"
+    , "Page"
+    , "liveApp"
+    , "quickStartDocument"
+    , "runPage"
+    , "run"
+    , "ViewId"
+    , "viewId"
+    , "ViewAction"
+    , "Eff"
+    , "button"
+    , "el"
+    , "el_"
+    , "Hyperbole"
+    , "Route"
+    , "routeRequest"
+    , "route"
+    , "layout"
+    , "Response"
+    , "ToParam"
+    , "FromParam"
+    , "Session"
+    , "FromQuery"
+    , "ToQuery"
+    , "lookupParam"
+    , "setParam"
+    , "DefaultParam"
+    , "Client"
+    ]
+
+
+-- returns lines of a top-level definition
+findTopLevel :: TopLevelDefinition -> SourceCode -> [Text]
+findTopLevel (TopLevelDefinition definition) source =
+  let rest = dropWhile (not . isTopLevel) source.lines
+   in dropWhileEnd isEmpty $ takeWhile isCurrentDefinition rest
+ where
+  isTopLevel = T.isPrefixOf definition
+  isEmpty = T.null
+  -- isBlankLine line = T.null $ T.strip line
+  isCurrentDefinition line =
+    isTopLevel line || not (isFullyOutdented line)
+  dropWhileEnd p as =
+    reverse $ dropWhile p $ reverse as
+
+
+isFullyOutdented :: Text -> Bool
+isFullyOutdented line =
+  case cs (T.take 1 line) of
+    "" -> False
+    [c] -> not $ isSpace c
+    _ -> False
diff --git a/hyperbole.cabal b/hyperbole.cabal
--- a/hyperbole.cabal
+++ b/hyperbole.cabal
@@ -1,11 +1,11 @@
 cabal-version: 2.2
 
--- This file has been generated from package.yaml by hpack version 0.37.0.
+-- This file has been generated from package.yaml by hpack version 0.39.5.
 --
 -- see: https://github.com/sol/hpack
 
 name:           hyperbole
-version:        0.6.1
+version:        0.7.0
 synopsis:       Interactive HTML apps using type-safe serverside Haskell
 description:    Interactive HTML applications with type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView.
 category:       Web, Network
@@ -17,8 +17,10 @@
 license-file:   LICENSE
 build-type:     Simple
 tested-with:
-    GHC == 9.8.2
-  , GHC == 9.6.6
+    GHC == 9.12.4
+  , GHC == 9.10.3
+  , GHC == 9.8.4
+  , GHC == 9.6.7
 extra-source-files:
     client/dist/hyperbole.js
     client/dist/hyperbole.js.map
@@ -35,17 +37,15 @@
   exposed-modules:
       Web.Hyperbole
       Web.Hyperbole.Application
+      Web.Hyperbole.Data.Argument
       Web.Hyperbole.Data.Cookie
       Web.Hyperbole.Data.Encoded
-      Web.Hyperbole.Data.JSON
-      Web.Hyperbole.Data.Param
       Web.Hyperbole.Data.QueryData
       Web.Hyperbole.Data.URI
       Web.Hyperbole.Document
       Web.Hyperbole.Effect.Client
       Web.Hyperbole.Effect.GenRandom
       Web.Hyperbole.Effect.Hyperbole
-      Web.Hyperbole.Effect.OAuth2
       Web.Hyperbole.Effect.Query
       Web.Hyperbole.Effect.Request
       Web.Hyperbole.Effect.Response
@@ -63,6 +63,7 @@
       Web.Hyperbole.Server.Message
       Web.Hyperbole.Server.Options
       Web.Hyperbole.Server.Socket
+      Web.Hyperbole.Server.Uploads
       Web.Hyperbole.Server.Wai
       Web.Hyperbole.TypeList
       Web.Hyperbole.Types.Client
@@ -92,9 +93,61 @@
       DataKinds
       DerivingStrategies
       DeriveAnyClass
+  ghc-options: -Wall -fdefer-typed-holes -F -pgmF=docgen
+  build-tool-depends:
+      hyperbole:docgen
+  build-depends:
+      aeson >=2.1.2.1 && <2.4
+    , atomic-css ==0.2.*
+    , attoparsec ==0.14.*
+    , attoparsec-aeson >=2.1 && <2.3
+    , base >=4.16 && <5
+    , bytestring >=0.11 && <0.13
+    , casing >=0.1.2 && <0.2
+    , containers >=0.6 && <1
+    , cookie >=0.4 && <0.6
+    , data-default ==0.8.*
+    , effectful >=2.4 && <3
+    , file-embed >=0.0.10 && <0.1
+    , filepath >=1.4 && <2
+    , http-client ==0.7.*
+    , http-client-tls >=0.3 && <0.5
+    , http-types ==0.12.*
+    , network >=3.1 && <4
+    , network-uri >=2.6.4.1 && <2.7
+    , random >=1.2 && <2
+    , resourcet >=1.2 && <2
+    , string-conversions ==0.4.*
+    , string-interpolate ==0.3.*
+    , text >=1.2 && <3
+    , time >=1.12 && <2
+    , wai >=3.2 && <4
+    , wai-extra >=3.1.10 && <3.2
+    , wai-websockets >=3.0 && <4
+    , warp >=3.3 && <4
+    , websockets >=0.12 && <0.14
+  default-language: GHC2021
+
+executable docgen
+  main-is: Docgen.hs
+  other-modules:
+      Paths_hyperbole
+  autogen-modules:
+      Paths_hyperbole
+  hs-source-dirs:
+      docs
+  default-extensions:
+      OverloadedStrings
+      OverloadedRecordDot
+      DuplicateRecordFields
+      NoFieldSelectors
+      TypeFamilies
+      DataKinds
+      DerivingStrategies
+      DeriveAnyClass
   ghc-options: -Wall -fdefer-typed-holes
   build-depends:
-      aeson >=2.1.2.1 && <2.3
+      aeson >=2.1.2.1 && <2.4
     , atomic-css ==0.2.*
     , attoparsec ==0.14.*
     , attoparsec-aeson >=2.1 && <2.3
@@ -104,21 +157,23 @@
     , containers >=0.6 && <1
     , cookie >=0.4 && <0.6
     , data-default ==0.8.*
+    , directory >=1.2 && <1.4
     , effectful >=2.4 && <3
     , file-embed >=0.0.10 && <0.1
     , filepath >=1.4 && <2
-    , http-api-data >=0.6 && <0.8
     , http-client ==0.7.*
-    , http-client-tls ==0.3.*
+    , http-client-tls >=0.3 && <0.5
     , http-types ==0.12.*
     , network >=3.1 && <4
     , network-uri >=2.6.4.1 && <2.7
     , random >=1.2 && <2
+    , resourcet >=1.2 && <2
     , string-conversions ==0.4.*
     , string-interpolate ==0.3.*
     , text >=1.2 && <3
     , time >=1.12 && <2
     , wai >=3.2 && <4
+    , wai-extra >=3.1.10 && <3.2
     , wai-websockets >=3.0 && <4
     , warp >=3.3 && <4
     , websockets >=0.12 && <0.14
@@ -128,9 +183,9 @@
   type: exitcode-stdio-1.0
   main-is: Spec.hs
   other-modules:
+      Test.ArgumentSpec
       Test.EncodedSpec
       Test.FormSpec
-      Test.ParamSpec
       Test.QuerySpec
       Test.RouteSpec
       Test.SessionSpec
@@ -156,7 +211,7 @@
   build-tool-depends:
       skeletest:skeletest-preprocessor
   build-depends:
-      aeson >=2.1.2.1 && <2.3
+      aeson >=2.1.2.1 && <2.4
     , atomic-css ==0.2.*
     , attoparsec ==0.14.*
     , attoparsec-aeson >=2.1 && <2.3
@@ -169,20 +224,21 @@
     , effectful >=2.4 && <3
     , file-embed >=0.0.10 && <0.1
     , filepath >=1.4 && <2
-    , http-api-data >=0.6 && <0.8
     , http-client ==0.7.*
-    , http-client-tls ==0.3.*
+    , http-client-tls >=0.3 && <0.5
     , http-types ==0.12.*
     , hyperbole
     , network >=3.1 && <4
     , network-uri >=2.6.4.1 && <2.7
     , random >=1.2 && <2
+    , resourcet >=1.2 && <2
     , skeletest
     , string-conversions ==0.4.*
     , string-interpolate ==0.3.*
     , text >=1.2 && <3
     , time >=1.12 && <2
     , wai >=3.2 && <4
+    , wai-extra >=3.1.10 && <3.2
     , wai-websockets >=3.0 && <4
     , warp >=3.3 && <4
     , websockets >=0.12 && <0.14
diff --git a/src/Web/Hyperbole.hs b/src/Web/Hyperbole.hs
--- a/src/Web/Hyperbole.hs
+++ b/src/Web/Hyperbole.hs
@@ -39,6 +39,8 @@
     -- ** Request #request#
   , request
   , Request (..)
+  , Form (..)
+  , UploadedFile (..)
 
     -- ** Response #response#
   , respondError
@@ -90,6 +92,7 @@
   , Option
 
     -- * Events
+  , inputValue
   , onClick
   , onDblClick
   , onMouseEnter
@@ -100,11 +103,15 @@
   , onKeyDown
   , onKeyUp
   , Key (..)
+  , InputValue (..)
 
     -- * Type-Safe Forms #forms#
     -- $forms
   , FromForm (..)
   , FromFormF (..)
+  , lookupField
+  , parseField
+  , FromField (..)
   , formData
   , GenFields (..)
   , fieldNames
@@ -125,6 +132,7 @@
   , select
   , checked
   , textarea
+  , fileInput
   , submit
   , View.placeholder
   , InputType (..)
@@ -137,8 +145,6 @@
 
     -- * Query Param Encoding #query-param#
   , QueryData
-  , ToParam (..)
-  , FromParam (..)
   , ToEncoded
   , FromEncoded
 
@@ -183,13 +189,12 @@
 import Web.Atomic.Types ()
 import Web.Hyperbole.Application
 import Web.Hyperbole.Data.Encoded (FromEncoded, ToEncoded)
-import Web.Hyperbole.Data.Param
 import Web.Hyperbole.Data.QueryData
 import Web.Hyperbole.Document
 import Web.Hyperbole.Effect.Client
 import Web.Hyperbole.Effect.Hyperbole
 import Web.Hyperbole.Effect.Query
-import Web.Hyperbole.Effect.Request
+import Web.Hyperbole.Effect.Request (inputValue)
 import Web.Hyperbole.Effect.Response
 import Web.Hyperbole.Effect.Session
 import Web.Hyperbole.HyperView
@@ -207,5 +212,3 @@
 
 Please visit https://hyperbole.live for documentation and examples
 -}
-
--- TODO: NSO link
diff --git a/src/Web/Hyperbole/Application.hs b/src/Web/Hyperbole/Application.hs
--- a/src/Web/Hyperbole/Application.hs
+++ b/src/Web/Hyperbole/Application.hs
@@ -6,6 +6,7 @@
   , liveAppWith
   , ServerOptions (..)
   , defaultErrorMessage
+  , defaultParseRequestBodyOptions
   , defaultError
   , socketApp
   , quickStartDocument
@@ -25,12 +26,12 @@
 import Network.WebSockets qualified as WS
 import Web.Hyperbole.Document
 import Web.Hyperbole.Effect.Hyperbole
-import Web.Hyperbole.Effect.Request (reqPath)
 import Web.Hyperbole.Effect.Response (notFound)
 import Web.Hyperbole.Route
 import Web.Hyperbole.Server.Options
 import Web.Hyperbole.Server.Socket (RunningActions, handleRequestSocket)
 import Web.Hyperbole.Server.Wai (handleRequestWai)
+import Web.Hyperbole.Types.Request
 import Web.Hyperbole.Types.Response
 
 
@@ -44,6 +45,7 @@
     ServerOptions
       { toDocument = doc
       , serverError = defaultError
+      , parseRequestBody = defaultParseRequestBodyOptions
       }
 
 
@@ -103,5 +105,5 @@
 -}
 routeRequest :: (Hyperbole :> es, Route route) => (route -> Eff es Response) -> Eff es Response
 routeRequest actions = do
-  pth <- reqPath
-  maybe notFound actions $ matchRoute pth
+  r <- request
+  maybe notFound actions $ matchRoute r.path
diff --git a/src/Web/Hyperbole/Data/Argument.hs b/src/Web/Hyperbole/Data/Argument.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Hyperbole/Data/Argument.hs
@@ -0,0 +1,129 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedLists #-}
+
+module Web.Hyperbole.Data.Argument where
+
+import Control.Applicative ((<|>))
+import Data.Aeson (FromJSON (..), ToJSON (..), Value (..))
+import Data.Aeson qualified as A
+import Data.Aeson.KeyMap (KeyMap, (!?))
+import Data.Aeson.Parser (json)
+import Data.Attoparsec.ByteString qualified as AB
+import Data.Attoparsec.ByteString qualified as Atto
+import Data.Attoparsec.ByteString.Char8 (sepBy)
+import Data.Attoparsec.ByteString.Char8 qualified as AC
+import Data.Bifunctor (first)
+import Data.Char (isAlphaNum, isUpper)
+import Data.Maybe (fromMaybe)
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import Data.Text qualified as T
+import GHC.Exts (IsList (..))
+import GHC.Generics
+
+
+data Argument
+  = JSON Value
+  deriving (Show, Eq, Ord, Generic)
+
+
+-------------------------------------------------------------------------------
+-- ARGUMENT ENCODING
+-------------------------------------------------------------------------------
+
+toArgument :: (ToJSON a) => a -> Argument
+toArgument = JSON . toJSON
+
+
+parseArgument :: forall a. (FromJSON a) => Argument -> Either String a
+parseArgument (JSON v) = do
+  case A.fromJSON v of
+    A.Success a -> pure a
+    A.Error e -> Left e
+
+
+-- | Parses both json and our custom constructor format
+argumentParser :: Atto.Parser Argument
+argumentParser =
+  JSON <$> (json <|> constructor <|> innerProduct)
+ where
+  innerProduct = do
+    _ <- AC.char '('
+    tag <- AC.takeWhile1 isAlphaNum
+    contents :: KeyMap Value <- innerContents <|> pure []
+    _ <- AC.char ')'
+    pure $ Object $ [("tag", String $ cs tag)] <> contents
+
+  innerContents = do
+    _ <- AC.char ' '
+    args <- json `sepBy` AC.space
+    pure $ case args of
+      [] -> []
+      [a] -> [("contents", a)]
+      as -> [("contents", Array $ fromList as)]
+
+  constructor = do
+    s <- AC.takeWhile1 isAlphaNum
+    pure $ String (cs s)
+
+
+-- decode a single argument
+decodeToArgument :: Text -> Either String Argument
+decodeToArgument inp =
+  first (\e -> "decodeArgument: " <> cs inp <> " failed with error: " <> e) $ AB.parseOnly argumentParser (cs inp)
+
+
+decodeArgument :: (FromJSON a) => Text -> Either String a
+decodeArgument inp = do
+  decodeToArgument inp >>= parseArgument
+
+
+encodeArgument :: (ToJSON a) => a -> Text
+encodeArgument a = encodeFromArgument $ toArgument a
+
+
+-- | JSON Encode argument, but alter it slightly to use raw strings for constructors without parameters, and switch to two-element sum encoding
+encodeFromArgument :: Argument -> Text
+encodeFromArgument (JSON v) = encodeJSON v
+
+
+-- | Encodes as JSON for simple values, but forces constructors into our custom show-like format
+encodeJSON :: Value -> Text
+encodeJSON = \case
+  (String s) ->
+    if isConstructorName s
+      then s
+      else encodeValue $ String s
+  (Object km) ->
+    simplifySumEncoding $ Object km
+  v -> encodeValue v
+ where
+  encodeValue :: Value -> Text
+  encodeValue = cs . A.encode
+
+  isConstructorName :: Text -> Bool
+  isConstructorName "" = False
+  isConstructorName t =
+    isUpper (T.head t) && T.all isAlphaNum t
+
+  simplifySumEncoding :: Value -> Text
+  simplifySumEncoding (Object o) = fromMaybe (encodeValue $ Object o) $ do
+    innerProduct o <|> simpleTag o
+  simplifySumEncoding v = encodeValue v
+
+  innerProduct :: KeyMap Value -> Maybe Text
+  innerProduct o = do
+    String t <- o !? "tag"
+    c <- o !? "contents"
+    let contents = case c of
+          Array a -> T.intercalate " " $ toList $ fmap encodeValue a
+          other -> encodeValue other
+    pure $ "(" <> t <> " " <> contents <> ")"
+
+  simpleTag :: KeyMap Value -> Maybe Text
+  simpleTag o = do
+    t <- o !? "tag"
+    case t of
+      String s -> pure $ "(" <> s <> ")"
+      v -> pure $ encodeValue v
diff --git a/src/Web/Hyperbole/Data/Cookie.hs b/src/Web/Hyperbole/Data/Cookie.hs
--- a/src/Web/Hyperbole/Data/Cookie.hs
+++ b/src/Web/Hyperbole/Data/Cookie.hs
@@ -78,4 +78,4 @@
 parseValue :: ByteString -> ByteString -> Either String Cookie
 parseValue k val = do
   let cval = CookieValue $ cs $ urlDecode True val
-  pure $ Cookie (cs k) Nothing (Just $ cval) True
+  pure $ Cookie (cs k) Nothing (Just cval) True
diff --git a/src/Web/Hyperbole/Data/Encoded.hs b/src/Web/Hyperbole/Data/Encoded.hs
--- a/src/Web/Hyperbole/Data/Encoded.hs
+++ b/src/Web/Hyperbole/Data/Encoded.hs
@@ -1,14 +1,14 @@
+{-# LANGUAGE BlockArguments #-}
 {-# LANGUAGE DefaultSignatures #-}
-{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE OverloadedLists #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 module Web.Hyperbole.Data.Encoded where
 
 import Data.Aeson (FromJSON (..), ToJSON (..), Value (..))
 import Data.Aeson qualified as A
 import Data.Attoparsec.ByteString qualified as AB
-import Data.Attoparsec.ByteString qualified as Atto
-import Data.Attoparsec.ByteString.Char8 (isSpace, sepBy, takeWhile1)
+import Data.Attoparsec.ByteString.Char8 (sepBy)
 import Data.Attoparsec.ByteString.Char8 qualified as AC
 import Data.Bifunctor (first)
 import Data.String (IsString)
@@ -16,7 +16,7 @@
 import Data.Text (Text)
 import Data.Text qualified as T
 import GHC.Generics
-import Web.Hyperbole.Data.Param
+import Web.Hyperbole.Data.Argument
 
 
 newtype ConName = ConName {text :: Text}
@@ -28,12 +28,13 @@
   mempty = ConName ""
 
 
-{- | Pretty Human Readable top-levelencoding for ViewAction and ViewId
+{- | Human Readable top-level encoding for ViewAction and ViewId
 For simple Sum and Product types it is equivalent to the Show/Read instance
 
 MyConstructor 1 2 3
+OtherConstructor "hello" 2
 -}
-data Encoded = Encoded ConName [ParamValue]
+data Encoded = Encoded ConName [Argument]
   deriving (Show, Eq, Ord)
 
 
@@ -69,7 +70,7 @@
 -- | Basic Encoding
 encodedToText :: Encoded -> Text
 encodedToText (Encoded con values) =
-  T.intercalate " " (con.text : fmap encodeParam values)
+  T.intercalate " " (con.text : fmap encodeFromArgument values)
 
 
 encodedParseText :: Text -> Either String Encoded
@@ -80,7 +81,7 @@
   encodedParser = do
     con <- AC.takeTill AC.isSpace
     AC.skipSpace
-    ps <- paramParser `sepBy` AC.char ' '
+    ps <- argumentParser `sepBy` AC.char ' '
     pure $ Encoded (ConName (cs con)) ps
 
 
@@ -111,12 +112,12 @@
   toEncoded = id
 instance ToEncoded () where
   toEncoded _ = mempty
-instance ToEncoded ParamValue where
-  toEncoded p = Encoded mempty [toParam p]
+instance ToEncoded Argument where
+  toEncoded a = Encoded mempty [a]
 instance ToEncoded Int where
-  toEncoded = toEncoded . toParam
+  toEncoded n = toEncoded $ JSON (Number $ fromIntegral n)
 instance ToEncoded Text where
-  toEncoded = toEncoded . toParam
+  toEncoded t = toEncoded $ JSON (String t)
 
 
 -- | Custom Encoding for embedding into web documents. Noteably used for 'ViewId' and 'ViewAction'
@@ -130,15 +131,23 @@
   parseEncoded = pure
 instance FromEncoded () where
   parseEncoded _ = pure ()
-instance FromEncoded ParamValue where
-  parseEncoded (Encoded _ ps) = do
-    case ps of
-      [p] -> parseParam p
-      _ -> Left $ "Expected single param value [param] but got: " <> show ps
+instance FromEncoded Argument where
+  parseEncoded (Encoded _ as) = do
+    case as of
+      [a] -> pure a
+      _ -> Left $ "Expected single argument value [arg] but got: " <> show as
 instance FromEncoded Int where
-  parseEncoded enc = parseEncoded enc >>= parseParam
+  parseEncoded enc = do
+    arg <- parseEncoded @Argument enc
+    case arg of
+      JSON (Number a) -> pure $ round a
+      other -> Left $ "Expected Int but got " <> show other
 instance FromEncoded Text where
-  parseEncoded enc = parseEncoded enc >>= parseParam
+  parseEncoded enc = do
+    arg <- parseEncoded @Argument enc
+    case arg of
+      JSON (String t) -> pure t
+      other -> Left $ "Expected Text but got " <> show other
 
 
 fromResult :: A.Result a -> Either String a
@@ -147,91 +156,6 @@
 
 
 -------------------------------------------------------------------------------
--- PARAM ENCODING
--------------------------------------------------------------------------------
--- Params need to be sanitized and escaped, because we want to use spaces to separate our params
--- Data.Param by default does not sanitize spaces
-
-paramParser :: Atto.Parser ParamValue
-paramParser = do
-  t <- takeWhile1 (not . isSpace)
-  pure $ decodeParam $ cs t
-
-
-decodeParam :: Text -> ParamValue
-decodeParam = \case
-  "|" -> ParamValue ""
-  t -> ParamValue $ desanitizeParamText t
-
-
--- Param encoding scheme:
---   Wire format must not contain bare spaces (field separator) or real newlines.
---   We use backslash as the escape character:
---     '\'  → "\\"    (escape backslash itself, so it cannot be confused with an escape prefix)
---     '\n' → "\n"    (literal backslash + 'n', for real newline characters)
---     '_'  → "\_"    (escape underscore, since bare underscore encodes space)
---     ' '  → "_"     (encode space as underscore)
---   Decoding is the single-pass reverse of the above.
---
--- The critical invariant: backslash is escaped FIRST on encode and unescaped
--- LAST on decode.  This ensures that JSON escape sequences (e.g. the two chars
--- '\' 'n' inside "[\"\n\"]") are treated as an escaped backslash followed by
--- a plain 'n', not as the param newline escape.
--- See: https://github.com/seanhess/hyperbole/issues/187
-
-desanitizeParamText :: Text -> Text
-desanitizeParamText = go
- where
-  go t = case T.uncons t of
-    Nothing -> ""
-    Just ('\\', rest) -> case T.uncons rest of
-      Just ('\\', rest') -> T.cons '\\' (go rest')   -- \\ → \
-      Just ('n', rest')  -> T.cons '\n' (go rest')   -- \n → newline
-      Just ('_', rest')  -> T.cons '_' (go rest')    -- \_ → _
-      _                  -> T.cons '\\' (go rest)    -- bare backslash (shouldn't occur)
-    Just ('_', rest) -> T.cons ' ' (go rest)         -- _ → space
-    Just (c, rest)   -> T.cons c (go rest)            -- other chars verbatim
-
-
---   | T.isSuffixOf "\\" seg = T.dropEnd 1 seg <> "_" <> txt
---   | otherwise = seg <> " " <> txt
-
--- foldr join "" $
--- where
---
--- join "" "" = " "
--- join "" " " = " "
--- join seg "" = seg
--- join seg txt
---   | T.isSuffixOf "\\" seg = T.dropEnd 1 seg <> "_" <> txt
---   | otherwise = seg <> " " <> txt
-
-encodeParam :: ParamValue -> Text
-encodeParam (ParamValue t) =
-  case t of
-    "" -> "|"
-    _ -> sanitizeParamText t
- where
-  -- Encode a param value for the wire format.
-  -- Backslash MUST be escaped first (before newline), otherwise a literal
-  -- backslash followed by 'n' in the input (e.g. from JSON encoding) would
-  -- be indistinguishable from the newline escape on the wire.
-  -- See: https://github.com/seanhess/hyperbole/issues/187
-  sanitizeParamText :: Text -> Text
-  sanitizeParamText =
-    T.replace " " "_" . T.replace "_" "\\_" . T.replace "\n" "\\n" . T.replace "\\" "\\\\"
-
-
--- decodeParamValue :: (FromParam a) => Text -> Either String a
--- decodeParamValue = parseParam . decodeParam
-
--- decodeParam :: Text -> ParamValue
--- decodeParam inp = do
---   case A.eitherDecode (cs inp) of
---     Left _ -> paramFromText inp
---     Right v -> ParamValue inp v
-
--------------------------------------------------------------------------------
 -- GENERICS
 -------------------------------------------------------------------------------
 
@@ -252,7 +176,6 @@
 
 
 instance GToEncoded U1 where
-  -- WARNING: not sure if this will work
   gToEncoded U1 = mempty
 
 
@@ -270,14 +193,16 @@
   gToEncoded (M1 f) = gToEncoded f
 
 
-instance (ToParam a) => GToEncoded (K1 R a) where
-  gToEncoded (K1 a) = Encoded mempty [toParam a]
+instance (ToJSON a) => GToEncoded (K1 R a) where
+  gToEncoded (K1 a) = do
+    let JSON val = toArgument a
+    Encoded mempty [JSON val]
 
 
 -- GFromEncoded: Generic ViewAction Decoding
 
 class GFromEncoded f where
-  gParseEncoded :: Encoded -> Either String (f p, [ParamValue])
+  gParseEncoded :: Encoded -> Either String (f p, [Argument])
 
 
 instance (GFromEncoded f, GFromEncoded g) => GFromEncoded (f :+: g) where
@@ -287,7 +212,7 @@
     case (el, er) of
       (Right (l, lvals), _) -> pure (L1 l, lvals)
       (_, Right (r, rvals)) -> pure (R1 r, rvals)
-      (Left _, Left _) ->
+      (Left _e1, Left _e2) ->
         Left $ "No matching sum constructor: " <> cs con.text <> " " <> cs (show vals)
 
 
@@ -322,11 +247,16 @@
     pure (M1 a, rest)
 
 
-instance (FromParam a) => GFromEncoded (K1 R a) where
+-- how can we *sometimes* parse it as user input?
+-- we can branch here based on something the client sends?
+-- wrap it in user input instead?
+-- newtype Input a = Input a
+-- then I can overload it here.
+instance (FromJSON a) => GFromEncoded (K1 R a) where
   gParseEncoded (Encoded con vals) = do
     case vals of
       (param : rest) -> do
-        case parseParam param of
+        case parseArgument param of
           -- consume one param
           Right a -> pure (K1 a, rest)
           Left e -> Left (cs e)
diff --git a/src/Web/Hyperbole/Data/JSON.hs b/src/Web/Hyperbole/Data/JSON.hs
deleted file mode 100644
--- a/src/Web/Hyperbole/Data/JSON.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-module Web.Hyperbole.Data.JSON (JSON (..), ToJSON, FromJSON) where
-
-import Data.Aeson as A
-import Data.String.Conversions (cs)
-import Web.Hyperbole.Data.Encoded
-import Web.Hyperbole.Data.Param
-
-
--- JSON Encoded Data ------------------------------------
-
--- | This type always encodes data via JSON
-newtype JSON a = JSON a
-
-
-instance (ToJSON a) => ToEncoded (JSON a) where
-  toEncoded (JSON a) = Encoded "" [jsonParam a]
-instance (FromJSON a) => FromEncoded (JSON a) where
-  parseEncoded (Encoded _ [ParamValue t]) = do
-    JSON <$> A.eitherDecode (cs t)
-  parseEncoded (Encoded _ prms) = do
-    Left $ "Could not parse JSON Encoded, expected one param: " <> show prms
diff --git a/src/Web/Hyperbole/Data/Param.hs b/src/Web/Hyperbole/Data/Param.hs
deleted file mode 100644
--- a/src/Web/Hyperbole/Data/Param.hs
+++ /dev/null
@@ -1,287 +0,0 @@
-{-# LANGUAGE DefaultSignatures #-}
-
-module Web.Hyperbole.Data.Param where
-
-import Data.Aeson (FromJSON, GFromJSON, GToJSON, Options (..), SumEncoding (..), ToJSON, Value (..), Zero, defaultOptions, genericParseJSON, genericToJSON)
-import Data.Aeson qualified as A
-import Data.Aeson.Types qualified as A
-import Data.Bifunctor (first)
-import Data.String (IsString (..))
-import Data.String.Conversions (cs)
-import Data.Text (Text)
-import Data.Time.Clock (UTCTime)
-import Data.Word
-import GHC.Exts (IsList (..))
-import GHC.Generics
-import Text.Read (readMaybe)
-import Web.HttpApiData (FromHttpApiData, ToHttpApiData)
-import Web.HttpApiData qualified as HttpApiData
-import Web.Hyperbole.Data.URI (URI (..), parseURIReference, uriToText)
-
-
-newtype Param = Param {text :: Text}
-  deriving newtype (Show, Eq, Ord, IsString)
-
-
--- | Encode arbitrarily complex data into url form encoded data
-newtype ParamValue = ParamValue {value :: Text}
-  deriving newtype (Ord, Eq)
-  deriving (Show, Generic)
-
-
-instance IsString ParamValue where
-  fromString s = ParamValue (cs s)
-
-
-{- | 'session's, 'form's, and 'query's all encode data as query strings. ToParam and FromParam control how a datatype is encoded to a parameter.
- -
-This is equivalent to Web.HttpApiData, which is missing some instances and has some strange defaults
-
-@
-#EMBED Example.Docs.Sessions data AppColor
-@
--}
-class ToParam a where
-  toParam :: a -> ParamValue
-  default toParam :: (Generic a, GToJSON Zero (Rep a)) => a -> ParamValue
-  toParam = genericToParam
-
-
-instance ToParam ParamValue where
-  toParam = id
-instance ToParam Int where
-  toParam = jsonParam
-instance ToParam Integer where
-  toParam = jsonParam
-instance ToParam Text where
-  toParam = toQueryParam
-instance {-# OVERLAPS #-} ToParam String where
-  toParam = toQueryParam
-instance ToParam Float where
-  toParam = jsonParam
-instance ToParam Double where
-  toParam = jsonParam
-instance ToParam Word where
-  toParam = jsonParam
-instance ToParam Word8 where
-  toParam = jsonParam
-instance ToParam Word16 where
-  toParam = jsonParam
-instance ToParam Word32 where
-  toParam = jsonParam
-instance ToParam Word64 where
-  toParam = jsonParam
-instance ToParam Bool where
-  toParam = jsonParam
-instance ToParam Char where
-  toParam = toQueryParam
-instance ToParam UTCTime where
-  toParam = toQueryParam
-instance ToParam URI where
-  toParam = toParam . uriToText
-instance ToParam Value where
-  toParam = jsonParam
-instance (ToParam a, ToParam b) => ToParam (a, b) where
-  toParam (a, b) = toParam [toParam a, toParam b]
-
-
-{- | Decode data from a 'query', 'session', or 'form' parameter value
-
-@
-#EMBED Example.Docs.Sessions data AppColor
-@
--}
-class FromParam a where
-  parseParam :: ParamValue -> Either String a
-  default parseParam :: (Generic a, GFromJSON Zero (Rep a)) => ParamValue -> Either String a
-  parseParam = genericParseParam
-
-
-  decodeFormValue :: Maybe Text -> Either String a
-  decodeFormValue mval = do
-    case mval of
-      Nothing -> Left "missing form field value"
-      Just t -> do
-        parseParam $ ParamValue t
-
-
--- decodeParamValue :: Text -> Either String a
--- decodeParamValue = parseParam . decodeParam
-
--- Permissive instances. Some of these come directly from forms!
-instance FromParam ParamValue where
-  parseParam = pure
-instance FromParam Int where
-  parseParam "" = pure 0
-  parseParam p = jsonParse p
-instance FromParam Integer where
-  parseParam "" = pure 0
-  parseParam p = jsonParse p
-instance FromParam Float where
-  parseParam "" = pure 0
-  parseParam p = jsonParse p
-instance FromParam Double where
-  parseParam "" = pure 0
-  parseParam p = jsonParse p
-instance FromParam Text where
-  parseParam = parseQueryParam
-instance (FromParam a, FromParam b) => FromParam (a, b) where
-  parseParam p = do
-    ps <- parseParam @[ParamValue] p
-    case ps of
-      [pa, pb] -> (,) <$> parseParam pa <*> parseParam pb
-      _ -> Left $ "Expected [a,b] but got: " <> cs p.value
-
-
--- -- we don't need to desanitize the text
--- parseFormField [inp] = do
---   parseParam $ ParamValue inp (String inp)
-
-instance {-# OVERLAPS #-} FromParam String where
-  parseParam p = cs <$> parseQueryParam @Text p
-
-
--- parseFormField sel f = do
---   inp :: Text <- first cs $ FE.parseUnique @Text (cs sel) f
---   parseParam $ ParamValue inp (String inp)
-instance FromParam Word where
-  parseParam = jsonParse
-instance FromParam Word8 where
-  parseParam = jsonParse
-instance FromParam Word16 where
-  parseParam = jsonParse
-instance FromParam Word32 where
-  parseParam = jsonParse
-instance FromParam Word64 where
-  parseParam = jsonParse
-instance FromParam Bool where
-  parseParam (ParamValue t) =
-    case t of
-      "on" -> pure True
-      "off" -> pure False
-      "" -> pure False
-      "false" -> pure False
-      "true" -> pure True
-      other -> Left $ "Could not parse bool param: " <> cs other
-
-
-  decodeFormValue Nothing = pure False
-  decodeFormValue (Just t) =
-    parseParam $ ParamValue t
-
-
-instance FromParam Char where
-  parseParam = parseQueryParam
-instance FromParam UTCTime where
-  parseParam = parseQueryParam
-instance FromParam Value where
-  parseParam = jsonParse
-
-
-instance FromParam URI where
-  parseParam (ParamValue t) = do
-    case parseURIReference (cs t) of
-      Nothing -> Left $ "Invalid URI: " <> cs t
-      Just u -> pure u
-
-
-instance {-# OVERLAPPABLE #-} (ToParam a) => ToParam [a] where
-  toParam as =
-    -- JSON encode the individual params
-    let ps :: [ParamValue] = fmap toParam as
-     in toParam $ Array $ fromList $ fmap (String . (.value)) ps
-instance {-# OVERLAPPABLE #-} (FromParam a) => FromParam [a] where
-  parseParam p = do
-    ts <- jsonParse @[Text] p
-    mapM (parseParam . ParamValue) ts
-
-
-instance (ToParam a) => ToParam (Maybe a) where
-  toParam Nothing = ParamValue "~"
-  toParam (Just a) = toParam a
-instance {-# OVERLAPPABLE #-} (FromParam a) => FromParam (Maybe a) where
-  parseParam (ParamValue "") = pure Nothing
-  parseParam (ParamValue "~") = pure Nothing
-  parseParam t = Just <$> parseParam @a t
-
-
-  decodeFormValue Nothing = pure Nothing
-  decodeFormValue (Just t) = do
-    parseParam @(Maybe a) (ParamValue t)
-
-
-instance {-# OVERLAPS #-} FromParam (Maybe Text) where
-  parseParam (ParamValue "~") = pure Nothing
-  -- keep empty strings, the default instance discards them
-  parseParam (ParamValue "") = pure (Just "")
-  parseParam t = Just <$> parseParam @Text t
-
-
-  decodeFormValue Nothing = pure Nothing
-  decodeFormValue (Just t) = do
-    parseParam @(Maybe Text) (ParamValue t)
-
-
-instance (ToParam a, ToParam b) => ToParam (Either a b) where
-  toParam (Left a) = toParam a
-  toParam (Right b) = toParam b
-instance (FromParam a, FromParam b) => FromParam (Either a b) where
-  parseParam val =
-    case parseParam @a val of
-      Right a -> pure $ Left a
-      Left _ -> do
-        case parseParam @b val of
-          Left _ -> Left $ "Could not parse Either param: " <> show val
-          Right b -> pure $ Right b
-
-
-parseQueryParam :: (FromHttpApiData a) => ParamValue -> Either String a
-parseQueryParam (ParamValue t) =
-  first cs $ HttpApiData.parseQueryParam t
-
-
-toQueryParam :: (ToHttpApiData a) => a -> ParamValue
-toQueryParam a =
-  ParamValue $ HttpApiData.toQueryParam a
-
-
--- | Encode a Show as a query param
-showParam :: (Show a) => a -> ParamValue
-showParam a = toQueryParam $ show a
-
-
--- | Decode a Read as a query param
-readParam :: (Read a) => ParamValue -> Either String a
-readParam p = do
-  str <- parseQueryParam p
-  case readMaybe str of
-    Nothing -> Left $ cs $ "Could not read query param: " <> str
-    Just a -> pure a
-
-
-genericToParam :: (Generic a, GToJSON Zero (Rep a)) => a -> ParamValue
-genericToParam a =
-  case genericToJSON jsonOptions a of
-    String t -> ParamValue t
-    other -> jsonParam other
-
-
-genericParseParam :: (Generic a, GFromJSON Zero (Rep a)) => ParamValue -> Either String a
-genericParseParam (ParamValue t) = do
-  val <- maybe (pure $ String t) pure $ A.decode (cs t)
-  A.parseEither (genericParseJSON jsonOptions) val
-
-
--- Encoding ------------------------------------------------------------
-
-jsonOptions :: A.Options
-jsonOptions = defaultOptions{sumEncoding = TwoElemArray}
-
-
-jsonParam :: (ToJSON a) => a -> ParamValue
-jsonParam a = ParamValue (cs $ A.encode a)
-
-
-jsonParse :: (FromJSON a) => ParamValue -> Either String a
-jsonParse (ParamValue t) = do
-  A.eitherDecode (cs t)
diff --git a/src/Web/Hyperbole/Data/QueryData.hs b/src/Web/Hyperbole/Data/QueryData.hs
--- a/src/Web/Hyperbole/Data/QueryData.hs
+++ b/src/Web/Hyperbole/Data/QueryData.hs
@@ -3,42 +3,69 @@
 
 module Web.Hyperbole.Data.QueryData where
 
-import Data.Aeson (Value (Null))
+import Control.Applicative ((<|>))
+import Data.Aeson (FromJSON (..), Result (..), ToJSON (..), Value (..), fromJSON)
+import Data.Aeson qualified as A
 import Data.ByteString (ByteString)
 import Data.Default (Default (..))
 import Data.Map.Strict (Map)
 import Data.Map.Strict qualified as M
 import Data.Maybe (fromMaybe)
+import Data.String (IsString)
 import Data.String.Conversions (cs)
 import Data.Text (Text, pack)
+import Data.Text qualified as T
 import GHC.Exts (IsList (..))
 import GHC.Generics
 import Network.HTTP.Types (Query, QueryItem, renderQuery)
 import Network.HTTP.Types qualified as HTTP
-import Web.Hyperbole.Data.Encoded (decodeParam, encodeParam)
-import Web.Hyperbole.Data.Param
 import Prelude hiding (lookup)
 
 
+newtype Param = Param {text :: Text}
+  deriving newtype (Show, Eq, Ord, IsString)
+
+
 -- | Key-value store for query params and sessions
-newtype QueryData = QueryData (Map Param ParamValue)
+newtype QueryData = QueryData (Map Param Text)
   deriving (Show)
   deriving newtype (Monoid, Semigroup)
 
 
 instance IsList QueryData where
-  type Item QueryData = (Param, ParamValue)
+  type Item QueryData = (Param, Text)
   fromList = QueryData . fromList
   toList (QueryData m) = toList m
 
 
-singleton :: (ToParam a) => Param -> a -> QueryData
-singleton key a = QueryData $ M.singleton key (toParam a)
+parseParam :: (FromJSON a) => Value -> Either String a
+parseParam v =
+  case fromJSON v of
+    Success a -> pure a
+    Error e -> Left e
 
 
-insert :: (ToParam a) => Param -> a -> QueryData -> QueryData
+-- the "default" encoding uses unquoted strings
+encodeParam :: (ToJSON a) => a -> Text
+encodeParam a = do
+  case A.toJSON a of
+    String s -> cs s
+    val -> cs $ A.encode val
+
+
+-- | Decode a parameter from JSON. Uses unquoted strings when possible
+decodeParam :: (FromJSON a) => Text -> Either String a
+decodeParam inp = maybe (Left $ "Could not decode Query Param: " <> cs inp) pure $ do
+  A.decode (cs inp) <|> A.decode (cs $ "\"" <> inp <> "\"")
+
+
+singleton :: (ToJSON a) => Param -> a -> QueryData
+singleton key a = QueryData $ M.singleton key (encodeParam a)
+
+
+insert :: (ToJSON a) => Param -> a -> QueryData -> QueryData
 insert p a (QueryData m) =
-  QueryData $ M.insert p (toParam a) m
+  QueryData $ M.insert p (encodeParam a) m
 
 
 insertAll :: (ToQuery a) => a -> QueryData -> QueryData
@@ -52,17 +79,17 @@
   QueryData $ M.delete p m
 
 
-lookup :: (FromParam a) => Param -> QueryData -> Maybe a
+lookup :: (FromJSON a) => Param -> QueryData -> Maybe a
 lookup k (QueryData m) = do
-  t <- M.lookup k m
-  either (const Nothing) pure $ parseParam t
+  t :: Text <- M.lookup k m
+  either (const Nothing) pure $ decodeParam t
 
 
-require :: (FromParam a) => Param -> QueryData -> Either String a
+require :: (FromJSON a) => Param -> QueryData -> Either String a
 require p (QueryData m) = do
   case M.lookup p m of
     Nothing -> Left $ "Missing Key: " <> cs p.text
-    Just val -> parseParam val
+    Just val -> decodeParam val
 
 
 filterKey :: (Param -> Bool) -> QueryData -> QueryData
@@ -74,44 +101,48 @@
 member k (QueryData qd) = M.member k qd
 
 
-elems :: QueryData -> [ParamValue]
+elems :: QueryData -> [Text]
 elems (QueryData m) = M.elems m
 
 
-render :: QueryData -> ByteString
+render :: QueryData -> Text
 render qd =
-  renderQuery False (HTTP.toQuery $ fromQueryData qd)
+  T.replace "%20" "+" $ cs $ renderQuery False (HTTP.toQuery $ fromQueryData qd)
 
 
-parse :: ByteString -> QueryData
+parse :: Text -> QueryData
 parse =
-  -- urlDecode True
-  queryData . HTTP.parseQuery
+  -- it already knows how to handle + signs on parse
+  queryData . HTTP.parseQuery . cs
 
 
 queryData :: Query -> QueryData
 queryData q =
   fromList $ fmap fromQueryItem q
  where
-  fromQueryItem :: QueryItem -> (Param, ParamValue)
-  fromQueryItem (key, mval) =
+  fromQueryItem :: QueryItem -> (Param, Text)
+  fromQueryItem (key, mval) = do
     (Param (cs key), fromParam mval)
 
-  fromParam :: Maybe ByteString -> ParamValue
-  fromParam Nothing = jsonParam Null
-  fromParam (Just t) = decodeParam (cs t)
+  -- do we include fields that don't have a value? Sure
+  fromParam :: Maybe ByteString -> Text
+  fromParam Nothing = ""
+  fromParam (Just t) = cs t
 
 
+-- Now we have our own thing!
+-- 1. ALWAYS encode strings without quotes
+-- 2. How do you distinguish between numbers and strings then?
+
+-- problem: strings are always quoted
+-- if we unquote them, they might be a string like "30"
 fromQueryData :: QueryData -> Query
 fromQueryData q =
   fmap toQueryItem $ toList q
  where
-  toQueryItem :: (Param, ParamValue) -> QueryItem
+  toQueryItem :: (Param, Text) -> QueryItem
   toQueryItem (Param prm, pval) =
-    (cs prm, Just $ toQueryValue pval)
-
-  toQueryValue :: ParamValue -> ByteString
-  toQueryValue = cs . encodeParam
+    (cs prm, Just $ cs pval)
 
 
 {- | Decode a type from a 'QueryData'. Missing fields are set to 'Data.Default.def'
@@ -187,7 +218,7 @@
   gParseQuery q = M1 <$> gParseQuery q
 
 
-instance {-# OVERLAPPABLE #-} (Selector s, FromParam a, Default a) => GFromQuery (M1 S s (K1 R a)) where
+instance {-# OVERLAPPABLE #-} (Selector s, FromJSON a, Default a) => GFromQuery (M1 S s (K1 R a)) where
   gParseQuery q = do
     let s = selName (undefined :: M1 S s (K1 R (f a)) p)
     let mval = lookup (Param $ pack s) q
@@ -219,7 +250,7 @@
   gToQuery (M1 f) = gToQuery f
 
 
-instance {-# OVERLAPPABLE #-} (Selector s, ToParam a, Eq a, Default a) => GToQuery (M1 S s (K1 R a)) where
+instance {-# OVERLAPPABLE #-} (Selector s, ToJSON a, Eq a, Default a) => GToQuery (M1 S s (K1 R a)) where
   gToQuery (M1 (K1 a))
     | a == def = mempty
     | otherwise =
@@ -235,7 +266,7 @@
         let sel = Param $ pack $ selName (undefined :: M1 S s (K1 R (f a)) p)
          in singleton sel a
 
--- instance {-# OVERLAPS #-} (Selector s, ToParam a, Eq a) => GToQuery (M1 S s (K1 R [a])) where
+-- instance {-# OVERLAPS #-} (Selector s, ToJSON a, Eq a) => GToQuery (M1 S s (K1 R [a])) where
 --   gToQuery (M1 (K1 a))
 --     | a == [] = mempty
 --     | otherwise =
diff --git a/src/Web/Hyperbole/Data/URI.hs b/src/Web/Hyperbole/Data/URI.hs
--- a/src/Web/Hyperbole/Data/URI.hs
+++ b/src/Web/Hyperbole/Data/URI.hs
@@ -9,6 +9,7 @@
   , Segment
   , path
   , parseURIReference
+  , parseURI
   , pathUri
   , uriToText
   , pathToText
@@ -34,7 +35,7 @@
 import Data.Text qualified as T
 import GHC.Exts (IsList (..))
 import Network.HTTP.Types (Query, QueryItem, parseQuery, renderQuery)
-import Network.URI (URI (..), URIAuth (..), parseURIReference, uriToString)
+import Network.URI (URI (..), URIAuth (..), parseURI, parseURIReference, uriToString)
 import Network.URI qualified as Network
 import Network.URI.Static (uri)
 import System.FilePath (normalise, (</>))
diff --git a/src/Web/Hyperbole/Document.hs b/src/Web/Hyperbole/Document.hs
--- a/src/Web/Hyperbole/Document.hs
+++ b/src/Web/Hyperbole/Document.hs
@@ -48,7 +48,7 @@
 @
 -}
 quickStartDocument :: BL.ByteString -> BL.ByteString
-quickStartDocument = document (mobileFriendly >> quickStart)
+quickStartDocument = document quickStart
 
 
 -- | A simple mobile-friendly header with all required embeds and live reload
diff --git a/src/Web/Hyperbole/Effect/GenRandom.hs b/src/Web/Hyperbole/Effect/GenRandom.hs
--- a/src/Web/Hyperbole/Effect/GenRandom.hs
+++ b/src/Web/Hyperbole/Effect/GenRandom.hs
@@ -8,8 +8,8 @@
 import Data.Text (Text)
 import Effectful
 import Effectful.Dispatch.Dynamic
+import GHC.Generics (Generic)
 import System.Random (Random, randomRIO)
-import Web.Hyperbole.Data.Param (FromParam, ToParam)
 
 
 data GenRandom :: Effect where
@@ -53,4 +53,5 @@
 
 
 newtype Token a = Token {value :: Text}
-  deriving newtype (FromJSON, ToJSON, FromParam, ToParam, Eq, Show, Read, Ord)
+  deriving newtype (FromJSON, ToJSON, Eq, Show, Read, Ord)
+  deriving (Generic)
diff --git a/src/Web/Hyperbole/Effect/Hyperbole.hs b/src/Web/Hyperbole/Effect/Hyperbole.hs
--- a/src/Web/Hyperbole/Effect/Hyperbole.hs
+++ b/src/Web/Hyperbole/Effect/Hyperbole.hs
@@ -5,6 +5,7 @@
 import Data.Aeson (Value)
 import Data.Text (Text)
 import Effectful
+import Effectful.Dispatch.Dynamic
 import Effectful.Error.Static
 import Effectful.State.Static.Local
 import Effectful.Writer.Static.Local
@@ -47,3 +48,8 @@
       , query = mempty
       , pageTitle = Nothing
       }
+
+
+-- | Return all information about the 'Request'
+request :: (Hyperbole :> es) => Eff es Request
+request = send GetRequest
diff --git a/src/Web/Hyperbole/Effect/OAuth2.hs b/src/Web/Hyperbole/Effect/OAuth2.hs
deleted file mode 100644
--- a/src/Web/Hyperbole/Effect/OAuth2.hs
+++ /dev/null
@@ -1,284 +0,0 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE DerivingVia #-}
-{-# LANGUAGE LambdaCase #-}
-
-module Web.Hyperbole.Effect.OAuth2
-  ( OAuth2 (..)
-  , authUrl
-  , validateCode
-  , exchangeAuth
-  , exchangeRefresh
-  , runOAuth2
-  , getConfigEnv
-  , Scopes (..)
-  , AuthFlow (..)
-  , Config (..)
-  , TokenType (..)
-  , Authenticated (..)
-  , Token (..)
-  , ClientId
-  , ClientSecret
-  , Code
-  , Access
-  , Refresh
-  , State
-  , Auth
-  , OAuth2Error (..)
-  ) where
-
-import Control.Monad (unless, when)
-import Data.Aeson (FromJSON (..), Options (..), ToJSON (..), Value (..), defaultOptions, eitherDecode, genericParseJSON, genericToJSON)
-import Data.ByteString.Lazy qualified as BL
-import Data.Default
-import Data.Maybe (isJust)
-import Data.String (IsString (..))
-import Data.String.Conversions (cs)
-import Data.Text (Text)
-import Data.Text qualified as T
-import Effectful
-import Effectful.Dispatch.Dynamic
-import Effectful.Environment
-import Effectful.Exception
-import GHC.Generics (Generic)
-import Network.HTTP.Client (HttpException, Request (..), RequestBody (..))
-import Network.HTTP.Client qualified as HTTP
-import Network.HTTP.Types (hAccept, hContentType)
-import Network.URI (parseURI)
-import Text.Casing (quietSnake)
-import Web.Hyperbole.Data.Encoded
-import Web.Hyperbole.Data.Param
-import Web.Hyperbole.Data.URI
-import Web.Hyperbole.Effect.GenRandom
-import Web.Hyperbole.Effect.Hyperbole
-import Web.Hyperbole.Effect.Query
-import Web.Hyperbole.Effect.Response (respondError)
-import Web.Hyperbole.Effect.Session (Session (..), deleteSession, saveSession, session)
-import Web.Hyperbole.Types.Response
-
-
-authUrl :: (OAuth2 :> es) => URI -> Scopes -> Eff es URI
-authUrl redirectUrl scopes = send $ AuthUrl redirectUrl scopes
-
-
-validateCode :: (OAuth2 :> es) => Eff es (Token Code)
-validateCode = send ValidateCode
-
-
-exchangeAuth :: (OAuth2 :> es) => Token Code -> Eff es Authenticated
-exchangeAuth authCode = send $ ExchangeAuth authCode
-
-
-exchangeRefresh :: (OAuth2 :> es) => Token Refresh -> Eff es Authenticated
-exchangeRefresh refToken = send $ ExchangeRefresh refToken
-
-
-data OAuth2 :: Effect where
-  AuthUrl :: URI -> Scopes -> OAuth2 m URI
-  ValidateCode :: OAuth2 m (Token Code)
-  ExchangeAuth :: Token Code -> OAuth2 m Authenticated
-  ExchangeRefresh :: Token Refresh -> OAuth2 m Authenticated
-
-
-type instance DispatchOf OAuth2 = 'Dynamic
-
-
-runOAuth2
-  :: (GenRandom :> es, IOE :> es, Hyperbole :> es)
-  => Config
-  -> HTTP.Manager
-  -> Eff (OAuth2 : es) a
-  -> Eff es a
-runOAuth2 cfg mgr = interpret $ \_ -> \case
-  AuthUrl red scopes -> do
-    state <- genRandomToken 6
-    let url = authorizationUrl cfg.authorize cfg.clientId red scopes state
-    saveSession $ AuthFlow red state
-    pure url
-  ValidateCode -> do
-    flow <- session @AuthFlow
-    validateRedirectParams flow
-  ExchangeAuth authCode -> do
-    flow <- session @AuthFlow
-    let params = tokenParams cfg.clientId cfg.clientSecret flow.redirect authCode
-    auth <- sendTokenRequest cfg mgr params
-    deleteSession @AuthFlow
-    pure auth
-  ExchangeRefresh refToken -> do
-    let params = refreshParams cfg.clientId cfg.clientSecret refToken
-    sendTokenRequest cfg mgr params
-
-
-{- | read oauth config from env. This is not required, you can obtain these secrets another way
-and configure the app however you please. Just pass the results into runOAuth2 in your app
--}
-getConfigEnv :: (Environment :> es) => Eff es Config
-getConfigEnv = do
-  clientId <- Token . cs <$> getEnv "OAUTH2_CLIENT_ID"
-  clientSecret <- Token . cs <$> getEnv "OAUTH2_CLIENT_SECRET"
-  authorize <- Endpoint <$> getEnvURI "OAUTH2_AUTHORIZE_ENDPOINT"
-  token <- Endpoint <$> getEnvURI "OAUTH2_TOKEN_ENDPOINT"
-  pure $ Config{clientId, clientSecret, authorize, token}
- where
-  getEnvURI n = do
-    str <- getEnv n
-    case parseURI str of
-      Nothing -> throwIO $ OAuth2BadEnv n str
-      Just u -> pure u
-
-
--- Types -------------------------------------------------
-
-newtype Scopes = Scopes [Text]
-  deriving (Show, Generic)
-  deriving anyclass (FromParam, ToParam)
-instance ToJSON Scopes where
-  toJSON (Scopes ss) = String $ T.unwords ss
-instance FromJSON Scopes where
-  parseJSON v = do
-    t <- parseJSON @String v
-    pure $ fromString t
-instance IsString Scopes where
-  fromString s = Scopes $ T.words $ cs s
-
-
-data ClientId
-data ClientSecret
-data Code
-data Refresh
-data Access
-data State
-data Auth
-
-#if (!MIN_VERSION_aeson(2,2,0))
-instance FromJSON URI
-instance ToJSON URI
-instance FromJSON URIAuth
-instance ToJSON URIAuth
-#endif
-
-
-data AuthFlow = AuthFlow
-  { redirect :: URI
-  , state :: Token State
-  }
-  deriving (Generic, FromEncoded, ToEncoded)
-instance Session AuthFlow where
-  sessionKey = "OAuth2AuthFlow"
-  cookiePath = Just "/"
-instance Default AuthFlow where
-  def = AuthFlow (pathUri "/") (Token mempty)
-
-
-data Config = Config
-  { clientId :: Token ClientId
-  , clientSecret :: Token ClientSecret
-  , authorize :: Endpoint Auth
-  , token :: Endpoint (Token ())
-  }
-
-
-data TokenType
-  = Bearer
-  deriving (Show, Read, Generic, ToParam, FromParam)
-instance ToJSON TokenType where
-  toJSON s = toJSON $ show s
-instance FromJSON TokenType where
-  parseJSON (String ttyp) | T.toLower ttyp == "bearer" = pure Bearer
-  parseJSON val = fail $ "expected TokenType but got " <> show val
-
-
-data Authenticated = Authenticated
-  { tokenType :: TokenType
-  , expiresIn :: Maybe Int
-  , scope :: Maybe Scopes
-  , accessToken :: Token Access
-  , refreshToken :: Maybe (Token Refresh)
-  }
-  deriving (Generic, Show, ToParam, FromParam, ToEncoded, FromEncoded)
-instance FromJSON Authenticated where
-  parseJSON = genericParseJSON defaultOptions{fieldLabelModifier = quietSnake}
-instance ToJSON Authenticated where
-  toJSON = genericToJSON defaultOptions{fieldLabelModifier = quietSnake}
-instance Session Authenticated where
-  sessionKey = "OAuth2Authenticated"
-  cookiePath = Just "/"
-
-
-data OAuth2Error
-  = OAuth2BadResponse String BL.ByteString
-  | OAuth2TokenRequest HttpException
-  | OAuth2BadEnv String String
-  deriving (Show, Exception)
-
-
--- Lower level --------------------------------------------------
-
-authorizationUrl :: Endpoint Auth -> Token ClientId -> URI -> Scopes -> Token State -> URI
-authorizationUrl (Endpoint auth) (Token cid) redUrl (Scopes scopes) (Token state) =
-  auth{uriQuery = cs $ renderQuery True authParams}
- where
-  authParams =
-    [ ("response_type", Just "code")
-    , ("client_id", Just $ cs cid)
-    , ("redirect_uri", Just $ cs $ uriToText redUrl)
-    , ("scope", Just $ cs $ T.intercalate " " scopes)
-    , ("state", Just $ cs state)
-    ]
-
-
-tokenParams :: Token ClientId -> Token ClientSecret -> URI -> Token Code -> Query
-tokenParams (Token cid) (Token sec) redUrl (Token ac) =
-  [ ("grant_type", Just "authorization_code")
-  , ("client_id", Just $ cs cid)
-  , ("client_secret", Just $ cs sec)
-  , ("redirect_uri", Just $ cs $ uriToText redUrl)
-  , ("code", Just $ cs ac)
-  ]
-
-
-refreshParams :: Token ClientId -> Token ClientSecret -> Token Refresh -> Query
-refreshParams (Token cid) (Token sec) (Token ref) =
-  [ ("grant_type", Just "refresh_token")
-  , ("client_id", Just $ cs cid)
-  , ("client_secret", Just $ cs sec)
-  , ("refresh_token", Just $ cs ref)
-  ]
-
-
-validateRedirectParams :: (Hyperbole :> es) => AuthFlow -> Eff es (Token Code)
-validateRedirectParams flow = do
-  err <- lookupParam @Text "error"
-
-  when (isJust err) $ do
-    desc <- param "error_description"
-    respondError $ ErrAuth desc
-
-  authState <- param @(Token State) "state"
-  authCode <- param @(Token Code) "code"
-
-  unless (flow.state == authState) $ do
-    respondError $ ErrAuth "Oauth2 State mismatch"
-
-  pure authCode
-
-
-sendTokenRequest :: (IOE :> es) => Config -> HTTP.Manager -> Query -> Eff es Authenticated
-sendTokenRequest cfg mgr params = do
-  baseReq <- HTTP.requestFromURI cfg.token.uri
-
-  let req =
-        baseReq
-          { method = "POST"
-          , requestBody = RequestBodyBS $ renderQuery False params
-          , requestHeaders =
-              [ (hContentType, "application/x-www-form-urlencoded")
-              , (hAccept, "application/json")
-              ]
-          }
-
-  res <- liftIO (HTTP.httpLbs req mgr) `catch` (throwIO . OAuth2TokenRequest)
-
-  let body = HTTP.responseBody res
-  case eitherDecode @Authenticated body of
-    Left e -> throwIO $ OAuth2BadResponse e body
-    Right tr -> pure tr
diff --git a/src/Web/Hyperbole/Effect/Query.hs b/src/Web/Hyperbole/Effect/Query.hs
--- a/src/Web/Hyperbole/Effect/Query.hs
+++ b/src/Web/Hyperbole/Effect/Query.hs
@@ -1,18 +1,17 @@
 module Web.Hyperbole.Effect.Query where
 
+import Data.Aeson (FromJSON, ToJSON)
 import Data.ByteString qualified as BS
 import Data.Default (Default (..))
 import Data.Maybe (fromMaybe)
 import Data.String.Conversions (cs)
 import Effectful
 import Effectful.Dispatch.Dynamic (send)
-import Web.Hyperbole.Data.Param (FromParam (..), Param, ToParam (..))
-import Web.Hyperbole.Data.QueryData (FromQuery (..), QueryData (..), ToQuery (..), queryData)
+import Web.Hyperbole.Data.QueryData (FromQuery (..), Param (..), QueryData (..), ToQuery (..), queryData)
 import Web.Hyperbole.Data.QueryData qualified as QueryData
-import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..))
-import Web.Hyperbole.Effect.Request (request)
+import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..), request)
 import Web.Hyperbole.Types.Client (Client (..), clientSetQuery)
-import Web.Hyperbole.Types.Request
+import Web.Hyperbole.Types.Request hiding (Param)
 import Web.Hyperbole.Types.Response
 import Prelude
 
@@ -57,13 +56,13 @@
   setQuery (mempty :: QueryData)
 
 
-{- | Parse a single query parameter. Return a 400 status if missing or if parsing fails. See 'FromParam'
+{- | Parse a single query parameter. Return a 400 status if missing or if parsing fails. See 'decodeParam'
 
 @
 #EMBED Example.Docs.Params page'
 @
 -}
-param :: (FromParam a, Hyperbole :> es) => Param -> Eff es a
+param :: (FromJSON a, Hyperbole :> es) => Param -> Eff es a
 param p = do
   q <- queryParams
   case QueryData.require p q of
@@ -78,18 +77,18 @@
 #EMBED Example.Docs.SideEffects page
 @
 -}
-lookupParam :: (FromParam a, Hyperbole :> es) => Param -> Eff es (Maybe a)
+lookupParam :: (FromJSON a, Hyperbole :> es) => Param -> Eff es (Maybe a)
 lookupParam p = do
   QueryData.lookup p <$> queryParams
 
 
-{- | Modify the client's querystring to set a single parameter. See 'ToParam'
+{- | Modify the client's querystring to set a single parameter. See 'encodeParam'
 
 @
 #EMBED Example.Docs.Params instance HyperView Message
 @
 -}
-setParam :: (ToParam a, Hyperbole :> es) => Param -> a -> Eff es ()
+setParam :: (ToJSON a, Hyperbole :> es) => Param -> a -> Eff es ()
 setParam key a = do
   modifyQueryData (QueryData.insert key a)
 
diff --git a/src/Web/Hyperbole/Effect/Request.hs b/src/Web/Hyperbole/Effect/Request.hs
--- a/src/Web/Hyperbole/Effect/Request.hs
+++ b/src/Web/Hyperbole/Effect/Request.hs
@@ -1,20 +1,13 @@
 module Web.Hyperbole.Effect.Request where
 
-import Data.String.Conversions (cs)
 import Effectful
-import Effectful.Dispatch.Dynamic
-import Web.FormUrlEncoded (Form, urlDecodeForm)
 import Web.Hyperbole.Data.URI (Path (..))
 import Web.Hyperbole.Effect.Hyperbole
+import Web.Hyperbole.Effect.Response (parseError)
+import Web.Hyperbole.HyperView.Input (InputValue (..))
 import Web.Hyperbole.Types.Request
-import Web.Hyperbole.Types.Response
 
 
--- | Return all information about the 'Request'
-request :: (Hyperbole :> es) => Eff es Request
-request = send GetRequest
-
-
 {- | Return the request path
 
 >>> reqPath
@@ -24,12 +17,18 @@
 reqPath = (.path) <$> request
 
 
-{- | Return the request body as a Web.FormUrlEncoded.Form
+{- | Return the request body as a 'Form'
 
 Prefer using Type-Safe 'Form's when possible
 -}
 formBody :: (Hyperbole :> es) => Eff es Form
 formBody = do
-  b <- (.body) <$> request
-  let ef = urlDecodeForm b
-  either (send . RespondNow . Err . ErrParse . cs) pure ef
+  (.form) <$> request
+
+
+inputValue :: (InputValue a, Hyperbole :> es) => Eff es a
+inputValue = do
+  t <- (.input) <$> request
+  case parseInputValue t of
+    Left err -> parseError $ "Input: " <> err
+    Right a -> pure a
diff --git a/src/Web/Hyperbole/Effect/Response.hs b/src/Web/Hyperbole/Effect/Response.hs
--- a/src/Web/Hyperbole/Effect/Response.hs
+++ b/src/Web/Hyperbole/Effect/Response.hs
@@ -1,11 +1,11 @@
 module Web.Hyperbole.Effect.Response where
 
+import Data.Aeson (ToJSON)
 import Data.Text (Text)
 import Effectful
 import Effectful.Dispatch.Dynamic
 import Effectful.Reader.Dynamic
 import Effectful.State.Dynamic
-import Web.Hyperbole.Data.Encoded
 import Web.Hyperbole.Data.URI
 import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..))
 import Web.Hyperbole.HyperView (ConcurrencyValue (..), HyperView (..), hyperUnsafe)
@@ -15,20 +15,20 @@
 
 
 -- | Respond with the given hyperview
-hyperView :: (HyperView id es, ToEncoded (ViewState id)) => id -> ViewState id -> View id () -> Eff es Response
+hyperView :: (HyperView id es, ToJSON (ViewState id)) => id -> ViewState id -> View id () -> Eff es Response
 hyperView i st vw = do
   let vid = TargetViewId (toViewId i)
   pure $ Response $ ViewUpdate vid $ renderBody $ hyperUnsafe i st vw
 
 
-pushUpdate :: (Hyperbole :> es, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => View id () -> Eff (Reader id : State (ViewState id) : es) ()
+pushUpdate :: (Hyperbole :> es, ViewId id, ToJSON (ViewState id), ConcurrencyValue (Concurrency id)) => View id () -> Eff (Reader id : State (ViewState id) : es) ()
 pushUpdate vw = do
   i <- viewId
   st <- get
   pushUpdateTo i st vw
 
 
-pushUpdateTo :: (Hyperbole :> es, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> Eff es ()
+pushUpdateTo :: (Hyperbole :> es, ViewId id, ToJSON (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> Eff es ()
 pushUpdateTo i st vw = do
   send $ PushUpdate $ ViewUpdate (TargetViewId $ toViewId i) $ renderBody $ hyperUnsafe i st vw
 
diff --git a/src/Web/Hyperbole/Effect/Session.hs b/src/Web/Hyperbole/Effect/Session.hs
--- a/src/Web/Hyperbole/Effect/Session.hs
+++ b/src/Web/Hyperbole/Effect/Session.hs
@@ -3,6 +3,8 @@
 
 module Web.Hyperbole.Effect.Session where
 
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Aeson qualified as A
 import Data.Default (Default (..))
 import Data.Maybe (fromMaybe)
 import Data.String.Conversions (cs)
@@ -11,11 +13,8 @@
 import Effectful.Dispatch.Dynamic
 import GHC.Generics
 import Web.Hyperbole.Data.Cookie as Cookie
-import Web.Hyperbole.Data.Encoded as Encoded
-import Web.Hyperbole.Data.Param
 import Web.Hyperbole.Data.URI (Path)
-import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..))
-import Web.Hyperbole.Effect.Request (request)
+import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..), request)
 import Web.Hyperbole.Types.Client (Client (..), clientModCookies)
 import Web.Hyperbole.Types.Request
 import Web.Hyperbole.Types.Response
@@ -50,15 +49,15 @@
 
   -- | Encode type to a a cookie value
   toCookie :: a -> CookieValue
-  default toCookie :: (ToEncoded a) => a -> CookieValue
-  toCookie = CookieValue . cs . Encoded.encode
+  default toCookie :: (ToJSON a) => a -> CookieValue
+  toCookie = CookieValue . cs . A.encode
 
 
   -- | Decode from a cookie value. Defaults to FromJSON
   parseCookie :: CookieValue -> Either String a
-  default parseCookie :: (FromEncoded a) => CookieValue -> Either String a
+  default parseCookie :: (FromJSON a) => CookieValue -> Either String a
   parseCookie (CookieValue bs) = do
-    Encoded.decodeEither (cs bs)
+    A.eitherDecode (cs bs)
 
 
 {- | Load data from a browser cookie. If it doesn't exist, the 'Default' instance is used
@@ -84,7 +83,10 @@
   mck <- Cookie.lookup key <$> sessionCookies
   case mck of
     Nothing -> pure Nothing
-    Just val -> Just <$> parseSession key val
+    Just cook ->
+      case parseCookie cook of
+        Left _ -> pure Nothing
+        Right a -> pure $ Just a
 
 
 {- | Persist datatypes in browser cookies
@@ -131,7 +133,7 @@
 
 
 -- | save a single datatype to a specific key in the session
-setCookie :: (ToParam a, Hyperbole :> es) => Cookie -> Eff es ()
+setCookie :: (ToJSON a, Hyperbole :> es) => Cookie -> Eff es ()
 setCookie ck = do
   modifyCookies (Cookie.insert ck)
 
diff --git a/src/Web/Hyperbole/HyperView/Event.hs b/src/Web/Hyperbole/HyperView/Event.hs
--- a/src/Web/Hyperbole/HyperView/Event.hs
+++ b/src/Web/Hyperbole/HyperView/Event.hs
@@ -54,17 +54,14 @@
 
 > input (onInput OnSearch) 250 id
 -}
-onInput :: (ViewAction (Action id), Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a
+onInput :: (ViewAction (Action id), Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a
 onInput a delay = do
-  att (eventName "input") (encodedToText $ toActionInput a) . att "data-delay" (cs $ show delay)
+  att (eventName "input") (encodedToText $ toAction a) . att "data-delay" (cs $ show delay)
 
 
--- WARNING: no way to do this generically right now, because toActionInput is specialized to Text
---   the change event DOES assume that the target has a string value
---   but, that doesn't let us implement dropdown
-onChange :: (ViewAction (Action id), Attributable a) => (value -> Action id) -> Attributes a -> Attributes a
+onChange :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a
 onChange a = do
-  att (eventName "change") (encodedToText $ toActionInput a)
+  att (eventName "change") (encodedToText $ toAction a)
 
 
 onSubmit :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a
@@ -105,17 +102,6 @@
   | Shift
   | OtherKey Text
   deriving (Show, Read)
-
-
--- | Serialize a constructor that expects a single input, like `data MyAction = GoSearch Text`
-toActionInput :: (ViewAction a) => (val -> a) -> Encoded
-toActionInput act =
-  -- laziness should let us drop the last item?
-  -- maybe... I bet it evaluates it strictly
-  let Encoded con vals = toAction (act undefined)
-   in if null vals
-        then Encoded con vals
-        else Encoded con (init vals)
 
 
 -- | Internal
diff --git a/src/Web/Hyperbole/HyperView/Forms.hs b/src/Web/Hyperbole/HyperView/Forms.hs
--- a/src/Web/Hyperbole/HyperView/Forms.hs
+++ b/src/Web/Hyperbole/HyperView/Forms.hs
@@ -1,10 +1,14 @@
 {-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE DefaultSignatures #-}
+{-# LANGUAGE LambdaCase #-}
 {-# LANGUAGE UndecidableInstances #-}
 
 module Web.Hyperbole.HyperView.Forms
   ( FromForm (..)
   , FromFormF (..)
+  , lookupField
+  , parseField
+  , FromField (..)
   , GenFields (..)
   , fieldNames
   , FieldName (..)
@@ -15,6 +19,7 @@
   , field
   , label
   , input
+  , fileInput
   , checkbox
   , radioGroup
   , radio
@@ -23,7 +28,6 @@
   , textarea
   , submit
   , formData
-  , FormOptions (..)
   , Validated (..)
   , isInvalid
   , invalidText
@@ -31,34 +35,37 @@
   , Identity
 
     -- * Re-exports
-  , FE.FromFormKey
   , Generic
   , GFieldsGen (..)
   , GenField (..)
-  , Form (..)
   )
 where
 
-import Data.Bifunctor (first)
+import Control.Applicative ((<|>))
+import Data.Aeson (FromJSON, ToJSON)
+import Data.Bifunctor (first, second)
+import Data.ByteString qualified as BS
 import Data.Functor.Identity (Identity (..))
 import Data.Kind (Type)
 import Data.Maybe (fromMaybe)
 import Data.String (IsString (..))
 import Data.String.Conversions (cs)
 import Data.Text (Text, pack)
+import Data.Time.Clock (UTCTime)
 import Effectful
 import GHC.Generics
+import Network.URI
 import Text.Casing (kebab)
+import Text.Read (readMaybe)
 import Web.Atomic.Types hiding (Selector)
-import Web.FormUrlEncoded (Form (..), FormOptions (..))
-import Web.FormUrlEncoded qualified as FE
-import Web.Hyperbole.Data.Param
+import Web.Hyperbole.Data.Argument
 import Web.Hyperbole.Effect.Hyperbole
-import Web.Hyperbole.Effect.Request
+import Web.Hyperbole.Effect.Request (formBody)
 import Web.Hyperbole.Effect.Response (parseError)
 import Web.Hyperbole.HyperView.Event (onSubmit)
 import Web.Hyperbole.HyperView.Input (Option (..), checked)
 import Web.Hyperbole.HyperView.Types
+import Web.Hyperbole.Types.Request
 import Web.Hyperbole.View
 
 
@@ -66,6 +73,9 @@
 -- FORM PARSING
 ------------------------------------------------------------------------------
 
+type ParamKey = BS.ByteString
+
+
 {- | Simple types that be decoded from form data
 
 @
@@ -73,20 +83,20 @@
 @
 -}
 class FromForm (form :: Type) where
-  fromForm :: FE.Form -> Either String form
-  default fromForm :: (Generic form, GFormParse (Rep form)) => FE.Form -> Either String form
+  fromForm :: Form -> Either String form
+  default fromForm :: (Generic form, GFormParse (Rep form)) => Form -> Either String form
   fromForm f = to <$> gFormParse f
 
 
-{- | A Higher-Kinded type that can be parsed from a 'Web.FormUrlEncoded.Form'
+{- | A Higher-Kinded type that can be parsed from a 'Form'
 
 @
 #EMBED Example.FormValidation data UserForm
 @
 -}
 class FromFormF (f :: (Type -> Type) -> Type) where
-  fromFormF :: FE.Form -> Either String (f Identity)
-  default fromFormF :: (Generic (f Identity), GFormParse (Rep (f Identity))) => FE.Form -> Either String (f Identity)
+  fromFormF :: Form -> Either String (f Identity)
+  default fromFormF :: (Generic (f Identity), GFormParse (Rep (f Identity))) => Form -> Either String (f Identity)
   fromFormF f = to <$> gFormParse f
 
 
@@ -104,6 +114,21 @@
   either parseError pure ef
 
 
+lookupField :: ParamKey -> Form -> Maybe FormParam
+lookupField key f = FormParam . cs <$> lookup key f.params <|> FileParam <$> lookup key f.files
+
+
+parseField :: forall a. (FromField a) => ParamKey -> Form -> Either String a
+parseField key f = do
+  let mt :: Maybe FormParam = lookupField key f
+  a <- first (\err -> cs key <> ": " <> err) $ fromField @a mt
+  pure a
+
+
+-- where
+--  -- lookupFile = FileField <$> lookup key req.files
+--  lookupParam = ParamField . ParamValue . cs <$>
+
 ------------------------------------------------------------------------------
 -- GEN FIELDS: Generate a type from selector names
 ------------------------------------------------------------------------------
@@ -180,7 +205,7 @@
 
 -- | Form FieldName. This is embeded as the name attribute, and refers to the key need to parse the form when submitted. See 'fieldNames'
 newtype FieldName a = FieldName {value :: Text}
-  deriving newtype (Show, IsString, FromParam, ToParam)
+  deriving newtype (Show, IsString, FromJSON, ToJSON)
 
 
 -- | Display a 'FormField'. See 'form' and 'Form'
@@ -218,8 +243,9 @@
   , inputName :: FieldName a
   }
   deriving (Generic)
-instance (ViewId id, FromParam id, ToParam id) => ViewId (Input id a) where
+instance (ViewId id, FromJSON id) => ViewId (Input id a) where
   type ViewState (Input id a) = ViewState id
+  toViewId inp = toViewId inp.id
 
 
 {- | label for a 'field'
@@ -229,6 +255,12 @@
 label = tag "label"
 
 
+fileInput :: forall id a. View (Input id a) ()
+fileInput = do
+  inp :: Input id a <- viewId
+  tag "input" @ att "type" "file" . name inp.inputName.value $ none
+
+
 -- | input for a 'field'
 input :: forall id a. InputType -> View (Input id a) ()
 input ft = do
@@ -263,7 +295,7 @@
   , defaultOption :: opt
   }
   deriving (Generic)
-instance (FromParam id, ToParam id, FromParam a, ToParam a, ToParam opt, FromParam opt) => ViewId (Radio id a opt) where
+instance (FromJSON id, FromJSON opt, ToJSON id, ToJSON opt) => ViewId (Radio id a opt) where
   type ViewState (Radio id a opt) = ViewState id
 
 
@@ -271,13 +303,13 @@
 radioGroup defOpt = runChildView (\(inp :: Input id a) -> Radio inp.id inp.inputName defOpt)
 
 
-radio :: forall id a opt. (Eq opt, ToParam opt) => opt -> View (Radio id a opt) ()
+radio :: forall id a opt. (Eq opt, ToJSON opt) => opt -> View (Radio id a opt) ()
 radio val = do
   rd :: Radio id a opt <- viewId
   tag "input"
     @ att "type" "radio"
     . name rd.inputName.value
-    . value (toParam val).value
+    . value (encodeArgument val)
     . checked (rd.defaultOption == val)
     $ none
 
@@ -391,11 +423,150 @@
 
 
 ------------------------------------------------------------------------------
+-- FormField
+------------------------------------------------------------------------------
+
+class FromField a where
+  fromField :: Maybe FormParam -> Either String a
+  default fromField :: (FromJSON a) => Maybe FormParam -> Either String a
+  fromField = argumentField
+
+
+argumentField :: (FromJSON a) => Maybe FormParam -> Either String a
+argumentField = \case
+  Nothing -> Left "Missing form field value"
+  Just (FormParam t) -> decodeArgument t
+  Just (FileParam _) ->
+    Left "Cannot parse uploaded file as param"
+
+
+readField :: (Read a) => Maybe FormParam -> Either String a
+readField = \case
+  Nothing -> Left "Missing form field value"
+  Just (FormParam p) -> do
+    case readMaybe (cs p) of
+      Nothing -> Left "Could not read form field"
+      Just a -> pure a
+  Just (FileParam _) ->
+    Left "Cannot read uploaded file as param"
+
+
+instance FromField Int where
+  fromField Nothing = pure 0
+  fromField (Just "") = pure 0
+  fromField (Just t) = readField (Just t)
+instance FromField Integer where
+  fromField Nothing = pure 0
+  fromField (Just "") = pure 0
+  fromField (Just t) = readField (Just t)
+instance FromField Float where
+  fromField Nothing = pure 0
+  fromField (Just "") = pure 0
+  fromField (Just t) = readField (Just t)
+instance FromField Double where
+  fromField Nothing = pure 0
+  fromField (Just "") = pure 0
+  fromField (Just t) = readField (Just t)
+
+
+instance FromField Bool where
+  fromField Nothing = pure False
+  fromField (Just (FileParam _)) = Left "Cannot parse file param as bool"
+  fromField (Just (FormParam p)) =
+    case p of
+      "on" -> pure True
+      "off" -> pure False
+      "" -> pure False
+      "false" -> pure False
+      "true" -> pure True
+      other -> Left $ "Could not parse bool form param: " <> cs other
+instance FromField Char where
+  fromField Nothing = Left "Missing form field value"
+  fromField (Just (FileParam _)) = Left "Cannot parse file param as Char"
+  fromField (Just (FormParam t)) =
+    case cs t of
+      (c : _) -> pure c
+      _ -> Left "Could not parse empty form field as char"
+instance FromField UTCTime where
+  fromField = readField
+instance FromField URI where
+  fromField = \case
+    Nothing -> Left "Missing form field value"
+    Just (FileParam _) -> Left "Cannot parse file param as Char"
+    Just (FormParam t) ->
+      case parseURIReference (cs t) of
+        Nothing -> Left "Invalid URI"
+        Just a -> pure a
+instance FromField String where
+  fromField t = second cs $ fromField @Text t
+instance FromField BS.ByteString where
+  fromField t = second cs $ fromField @Text t
+instance FromField Text where
+  fromField = \case
+    Nothing -> pure ""
+    Just (FormParam t) -> pure t
+    Just (FileParam _) -> Left "Cannot parse FileParam as Text"
+
+
+instance {-# OVERLAPPABLE #-} (FromField a) => FromField (Maybe a) where
+  fromField Nothing = pure Nothing
+  fromField (Just "") = pure Nothing
+  fromField (Just a) = Just <$> fromField @a (Just a)
+instance {-# OVERLAPS #-} FromField (Maybe BS.ByteString) where
+  fromField Nothing = pure Nothing
+  fromField (Just "") = pure $ Just ""
+  fromField (Just a) = Just <$> fromField @BS.ByteString (Just a)
+instance {-# OVERLAPS #-} FromField (Maybe Text) where
+  fromField Nothing = pure Nothing
+  fromField (Just "") = pure $ Just ""
+  fromField (Just a) = Just <$> fromField @Text (Just a)
+instance {-# OVERLAPS #-} FromField (Maybe String) where
+  fromField Nothing = pure Nothing
+  fromField (Just "") = pure $ Just ""
+  fromField (Just a) = Just <$> fromField @String (Just a)
+instance {-# OVERLAPPABLE #-} (FromField a, FromField b) => FromField (Either a b) where
+  fromField = \case
+    Nothing -> Left "Missing form field value"
+    Just p ->
+      case fromField @a (Just p) of
+        Right a -> pure $ Left a
+        Left _ -> pure <$> fromField @b (Just p)
+instance FromField UploadedFile where
+  fromField = \case
+    Nothing -> Left "Missing file upload"
+    Just (FormParam "") -> Left "Missing file upload"
+    Just (FormParam t) -> Left $ "Cannot parse FormParam as UploadedFile: " <> cs t
+    Just (FileParam f) ->
+      if f.fileName == mempty
+        then Left "Empty file uploaded"
+        else pure f
+instance {-# OVERLAPS #-} FromField (Maybe UploadedFile) where
+  fromField = \case
+    Nothing -> pure Nothing
+    Just (FileParam f) -> do
+      if f.fileName == mempty
+        then pure Nothing
+        else pure $ Just f
+    other -> fromField other
+
+
+-- these instances don't make a lot of sense
+-- instance (FromField a, FromField b) => FromField (a, b)
+-- instance (FromField a, FromField b, FromField c) => FromField (a, b, c)
+-- instance {-# OVERLAPPABLE #-} (FromParam a) => FromField [a]
+-- instance FromField Value
+-- instance FromField Word
+-- instance FromField Word8
+-- instance FromField Word16
+-- instance FromField Word32
+-- instance FromField Word64
+
+------------------------------------------------------------------------------
 -- GENERIC FORM PARSE
 ------------------------------------------------------------------------------
 
 class GFormParse f where
-  gFormParse :: FE.Form -> Either String (f p)
+  gFormParse :: Form -> Either String (f p)
 
 
 instance (GFormParse f, GFormParse g) => GFormParse (f :*: g) where
@@ -413,23 +584,90 @@
   gFormParse f = M1 <$> gFormParse f
 
 
--- TODO: need a bool instance?
--- TODO: need a Maybe a instance?
-instance (Selector s, FromParam a) => GFormParse (M1 S s (K1 R a)) where
-  -- these CANNOT be json encoded, they are encoded by the browser
+instance (Selector s, FromField a) => GFormParse (M1 S s (K1 R a)) where
   gFormParse f = do
     let sel = selName (undefined :: M1 S s (K1 R (f a)) p)
-    mt :: Maybe Text <- first cs $ FE.lookupMaybe (cs sel) f
-    a <- first (\err -> sel <> ": " <> err) $ decodeFormValue mt
-    pure $ M1 . K1 $ a
+    a <- parseField (cs sel) f
+    pure $ M1 $ K1 a
 
 
--- instance {-# OVERLAPPING #-} (Selector s, FromParam a) => GFormParse (M1 S s (K1 R (Maybe a))) where
---   gFormParse f = do
---     let sel = selName (undefined :: M1 S s (K1 R (f a)) p)
---     mt :: Maybe Text <- first cs $ FE.lookupMaybe (cs sel) f
---     ma :: Maybe a <- maybe (pure Nothing) (parseParam . decodeParam) mt
---     pure $ M1 . K1 $ ma
+-- class GFieldParse f where
+--   gFieldParse :: String -> Maybe ParamValue -> Either String (f p)
+--
+--
+-- instance {-# OVERLAPPABLE #-} (FromJSON a) => GFieldParse (K1 R a) where
+--   gFieldParse sel Nothing = Left $ "Missing form field: " <> sel
+--   gFieldParse _ (Just (ParamText t)) = K1 <$> decodeArgument t
+--   gFieldParse sel (Just _) = Left $ "Unexpected file param: " <> sel
+--
+--
+-- instance {-# OVERLAPS #-} (GFieldParse (K1 R a)) => GFieldParse (K1 R (Maybe a)) where
+--   gFieldParse _ Nothing = pure $ K1 Nothing
+--   gFieldParse _ (Just (ParamText "")) = pure $ K1 Nothing -- an empty string should be Nothing unless overriden
+--   gFieldParse sel (Just p) = do
+--     K1 a <- gFieldParse @(K1 R a) sel (Just p)
+--     pure $ K1 $ Just a
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R Bool) where
+--   gFieldParse _ Nothing = pure $ K1 False
+--   gFieldParse sel (Just t) = do
+--     K1 <$> case t of
+--       "on" -> pure True
+--       "off" -> pure False
+--       "" -> pure False
+--       "false" -> pure False
+--       "true" -> pure True
+--       ParamText other -> Left $ "Could not parse bool field: " <> sel <> " = " <> cs other
+--       ParamFile _ -> Left $ "Unexpected file param: " <> sel
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R (Maybe Text)) where
+--   gFieldParse _ Nothing = pure $ K1 Nothing
+--   gFieldParse sel inp = do
+--     K1 t <- gFieldParse @(K1 R Text) sel inp
+--     pure $ K1 $ Just t
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R Text) where
+--   gFieldParse _ Nothing = pure $ K1 ""
+--   gFieldParse _ (Just "") = pure $ K1 ""
+--   gFieldParse _ (Just (ParamText t)) = K1 <$> decodeArgument ("\"" <> t <> "\"")
+--   gFieldParse sel (Just _) = Left $ "Unexpected file param: " <> sel
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R Int) where
+--   gFieldParse _ Nothing = pure $ K1 0
+--   gFieldParse _ (Just "") = pure $ K1 0
+--   gFieldParse _ (Just (ParamText t)) = K1 <$> decodeArgument t
+--   gFieldParse sel (Just _) = Left $ "Unexpected file param: " <> sel
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R Integer) where
+--   gFieldParse _ Nothing = pure $ K1 0
+--   gFieldParse _ (Just "") = pure $ K1 0
+--   gFieldParse _ (Just (ParamText t)) = K1 <$> decodeArgument t
+--   gFieldParse sel (Just _) = Left $ "Unexpected file param: " <> sel
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R Float) where
+--   gFieldParse _ Nothing = pure $ K1 0
+--   gFieldParse _ (Just "") = pure $ K1 0
+--   gFieldParse _ (Just (ParamText t)) = K1 <$> decodeArgument t
+--   gFieldParse sel (Just _) = Left $ "Unexpected file param: " <> sel
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R Double) where
+--   gFieldParse _ Nothing = pure $ K1 0
+--   gFieldParse _ (Just "") = pure $ K1 0
+--   gFieldParse _ (Just (ParamText t)) = K1 <$> decodeArgument t
+--   gFieldParse sel (Just _) = Left $ "Unexpected file param: " <> sel
+--
+--
+-- instance {-# OVERLAPS #-} GFieldParse (K1 R UploadedFile) where
+--   gFieldParse sel Nothing = Left $ "Missing form field: " <> sel
+--   gFieldParse _ (Just (ParamFile f)) = Right $ K1 f
+--   gFieldParse sel (Just (ParamText _)) = Left $ "Unexpected text param: " <> sel
 
 ------------------------------------------------------------------------------
 -- GENERIC GENERATE FIELDS
diff --git a/src/Web/Hyperbole/HyperView/Handled.hs b/src/Web/Hyperbole/HyperView/Handled.hs
--- a/src/Web/Hyperbole/HyperView/Handled.hs
+++ b/src/Web/Hyperbole/HyperView/Handled.hs
@@ -91,8 +91,7 @@
 -- TODO: Report which view requires the missing one
 type family CheckDescendents id ctx :: Constraint where
   CheckDescendents id (Root total) =
-    ( AllInPage (ValidDescendents id) total
-    )
+    (AllInPage (ValidDescendents id) total)
   CheckDescendents id ctx = ()
 
 
diff --git a/src/Web/Hyperbole/HyperView/Hyper.hs b/src/Web/Hyperbole/HyperView/Hyper.hs
--- a/src/Web/Hyperbole/HyperView/Hyper.hs
+++ b/src/Web/Hyperbole/HyperView/Hyper.hs
@@ -2,6 +2,9 @@
 
 module Web.Hyperbole.HyperView.Hyper where
 
+import Data.Aeson (ToJSON)
+import Data.Aeson qualified as A
+import Data.String.Conversions (cs)
 import Web.Atomic.Types
 import Web.Hyperbole.Data.Encoded as Encoded
 import Web.Hyperbole.HyperView.Handled (HyperViewHandled)
@@ -25,9 +28,15 @@
 hyper vid = hyperState vid ()
 
 
+{- | Embed a 'HyperView' into a page or another 'View' with a starting 'ViewState'
+
+@
+#EMBED Example.State.ViewState page
+@
+-}
 hyperState
   :: forall id ctx
-   . (HyperViewHandled id ctx, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id))
+   . (HyperViewHandled id ctx, ViewId id, ToJSON (ViewState id), ConcurrencyValue (Concurrency id))
   => id
   -> ViewState id
   -> View id ()
@@ -35,7 +44,7 @@
 hyperState = hyperUnsafe
 
 
-hyperUnsafe :: forall id ctx. (ViewId id, ViewState id ~ ViewState id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> View ctx ()
+hyperUnsafe :: forall id ctx. (ViewId id, ViewState id ~ ViewState id, ToJSON (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> View ctx ()
 hyperUnsafe vid st vw = do
   tag "div" @ att "id" (encodedToText $ toViewId vid) . state . concurrency $
     runViewContext vid st vw
@@ -46,6 +55,7 @@
       Replace -> att "data-concurrency" (encode Replace)
 
   state =
-    if encode st == mempty
-      then id
-      else att "data-state" (encode st)
+    let enc = A.encode st
+     in if enc == mempty
+          then id
+          else att "data-state" (cs enc)
diff --git a/src/Web/Hyperbole/HyperView/Input.hs b/src/Web/Hyperbole/HyperView/Input.hs
--- a/src/Web/Hyperbole/HyperView/Input.hs
+++ b/src/Web/Hyperbole/HyperView/Input.hs
@@ -1,10 +1,14 @@
+{-# LANGUAGE DefaultSignatures #-}
+{-# LANGUAGE UndecidableInstances #-}
+
 module Web.Hyperbole.HyperView.Input where
 
+import Data.Aeson (FromJSON, ToJSON)
 import Data.String.Conversions (cs)
 import Data.Text (Text)
 import GHC.Generics (Generic)
 import Web.Atomic.Types
-import Web.Hyperbole.Data.Param (FromParam, ParamValue (..), ToParam (..))
+import Web.Hyperbole.Data.Argument (decodeArgument, encodeArgument)
 import Web.Hyperbole.HyperView.Event (DelayMs, onChange, onClick, onInput)
 import Web.Hyperbole.HyperView.Types (HyperView (..))
 import Web.Hyperbole.Route (Route (..), routeUri)
@@ -30,8 +34,8 @@
 -}
 dropdown
   :: forall opt id
-   . (ViewAction (Action id))
-  => (opt -> Action id)
+   . (ViewAction (Action id), FromJSON opt)
+  => Action id
   -> opt -- default option
   -> View (Option id opt) ()
   -> View id ()
@@ -45,15 +49,23 @@
 -- | An option for a 'dropdown' or 'select'
 option
   :: forall opt id
-   . (ViewAction (Action id), Eq opt, ToParam opt)
+   . (ViewAction (Action id), Eq opt, ToJSON opt)
   => opt
   -> Text
   -> View (Option id opt) ()
 option opt cnt = do
   os :: Option id opt <- viewId
-  tag "option" @ att "value" (toParam opt).value @ selected (os.defaultOption == opt) $ text cnt
+  tag "option" @ att "value" (encodeOption opt) @ selected (os.defaultOption == opt) $ text cnt
 
 
+encodeOption :: (ToJSON opt) => opt -> Text
+encodeOption opt =
+  case encodeArgument opt of
+    -- For options, an empty string makes much more sense than null for empty values
+    "null" -> ""
+    other -> other
+
+
 -- | sets selected = true if the 'dropdown' predicate returns True
 selected :: (Attributable h) => Bool -> Attributes h -> Attributes h
 selected b = if b then att "selected" "true" else id
@@ -67,7 +79,7 @@
   deriving (Generic)
 
 
-instance (ToParam id, ToParam opt, FromParam id, FromParam opt) => ViewId (Option id opt) where
+instance (ToJSON id, ToJSON opt, FromJSON id, FromJSON opt) => ViewId (Option id opt) where
   type ViewState (Option id opt) = ViewState id
 
 
@@ -77,7 +89,7 @@
 #EMBED Example.Errors viewSearchUsers
 @
 -}
-search :: (ViewAction (Action id)) => (Text -> Action id) -> DelayMs -> View id ()
+search :: (ViewAction (Action id)) => Action id -> DelayMs -> View id ()
 search go delay = do
   tag "input" none @ onInput go delay
 
@@ -96,3 +108,47 @@
 -}
 route :: (Route a) => a -> View c () -> View c ()
 route r = link (routeUri r)
+
+
+-- instance {-# OVERLAPPABLE #-} (FromJSON a) => UserInput (Maybe a) where
+--   parseInput "" = pure Nothing
+--   parseInput t = pure $ A.decode (cs t)
+--
+--
+-- instance {-# OVERLAPS #-} UserInput (Maybe Text) where
+--   parseInput = pure . Just
+
+class InputValue a where
+  parseInputValue :: Text -> Either String a
+  default parseInputValue :: (FromJSON a) => Text -> Either String a
+  parseInputValue = decodeArgument
+
+
+instance InputValue Text where
+  parseInputValue = pure
+instance InputValue Int where
+  parseInputValue "" = Right 0
+  parseInputValue t = decodeArgument t
+instance InputValue Float where
+  parseInputValue "" = Right 0
+  parseInputValue t = decodeArgument t
+instance InputValue Double where
+  parseInputValue "" = Right 0
+  parseInputValue t = decodeArgument t
+instance InputValue Integer where
+  parseInputValue "" = Right 0
+  parseInputValue t = decodeArgument t
+
+
+instance {-# OVERLAPPABLE #-} (InputValue a) => InputValue (Maybe a) where
+  parseInputValue "null" = pure Nothing
+  parseInputValue "" = pure Nothing
+  parseInputValue t = Just <$> parseInputValue t
+
+
+instance {-# OVERLAPS #-} InputValue (Maybe Text) where
+  parseInputValue t = pure $ Just t
+
+
+instance {-# OVERLAPS #-} InputValue (Maybe String) where
+  parseInputValue t = pure $ Just (cs t)
diff --git a/src/Web/Hyperbole/HyperView/Types.hs b/src/Web/Hyperbole/HyperView/Types.hs
--- a/src/Web/Hyperbole/HyperView/Types.hs
+++ b/src/Web/Hyperbole/HyperView/Types.hs
@@ -27,16 +27,18 @@
 @
 -}
 class (ViewId id, ViewAction (Action id), ConcurrencyValue (Concurrency id)) => HyperView id es where
-  -- | Outline all actions that are permitted in this HyperView
-  --
-  -- > data Action Message = SetMessage Text | ClearMessage
-  -- >   deriving (Generic, ViewAction)
+  {- | Outline all actions that are permitted in this HyperView
+
+  > data Action Message = SetMessage Text | ClearMessage
+  >   deriving (Generic, ViewAction)
+  -}
   data Action id
 
 
-  -- | Include any child hyperviews here. The compiler will make sure that the page knows how to handle them
-  --
-  -- > type Require Messages = '[ChildView]
+  {- | Include any child hyperviews here. The compiler will make sure that the page knows how to handle them
+
+  > type Require Messages = '[ChildView]
+  -}
   type Require id :: [Type]
 
 
@@ -46,19 +48,21 @@
   -- type ViewState id :: Type
   -- type ViewState id = ()
 
-  -- | Control how overlapping actions are handled. 'Drop' by default
-  --
-  -- > type Concurrency Autocomplete = Replace
+  {- | Control how overlapping actions are handled. 'Drop' by default
+
+  > type Concurrency Autocomplete = Replace
+  -}
   type Concurrency id :: ConcurrencyMode
 
 
   type Concurrency id = Drop
 
 
-  -- | Specify how the view should be updated for each Action
-  --
-  -- > update (SetMessage msg) = pure $ messageView msg
-  -- > update ClearMessage = pure $ messageView ""
+  {- | Specify how the view should be updated for each Action
+
+  > update (SetMessage msg) = pure $ messageView msg
+  > update ClearMessage = pure $ messageView ""
+  -}
   update :: (Hyperbole :> es) => Action id -> Eff (Reader id : State (ViewState id) : es) (View id ())
 
 
diff --git a/src/Web/Hyperbole/Server/Handler.hs b/src/Web/Hyperbole/Server/Handler.hs
--- a/src/Web/Hyperbole/Server/Handler.hs
+++ b/src/Web/Hyperbole/Server/Handler.hs
@@ -3,6 +3,8 @@
 
 module Web.Hyperbole.Server.Handler where
 
+import Data.Aeson (FromJSON, ToJSON, Value)
+import Data.Aeson qualified as A
 import Data.Kind (Type)
 import Effectful
 import Effectful.Dispatch.Dynamic
@@ -10,7 +12,7 @@
 import Effectful.State.Dynamic
 import Web.Hyperbole.Data.Encoded
 import Web.Hyperbole.Effect.Hyperbole
-import Web.Hyperbole.Effect.Response (hyperView, respondError)
+import Web.Hyperbole.Effect.Response (hyperView, parseError, respondError)
 import Web.Hyperbole.HyperView
 import Web.Hyperbole.Types.Event
 import Web.Hyperbole.Types.Request
@@ -19,14 +21,14 @@
 
 
 class RunHandlers (views :: [Type]) es where
-  runHandlers :: (Hyperbole :> es) => Event TargetViewId Encoded Encoded -> Eff es (Maybe Response)
+  runHandlers :: (Hyperbole :> es) => Event TargetViewId Encoded Value -> Eff es (Maybe Response)
 
 
 instance RunHandlers '[] es where
   runHandlers _ = pure Nothing
 
 
-instance (HyperView view es, ToEncoded (ViewState view), FromEncoded (ViewState view), RunHandlers views es) => RunHandlers (view : views) es where
+instance (HyperView view es, ToJSON (ViewState view), FromJSON (ViewState view), RunHandlers views es) => RunHandlers (view : views) es where
   runHandlers rawEvent = do
     mr <- runHandler @view rawEvent (update @view)
     case mr of
@@ -36,8 +38,8 @@
 
 runHandler
   :: forall id es
-   . (HyperView id es, ToEncoded (ViewState id), FromEncoded (ViewState id), Hyperbole :> es)
-  => Event TargetViewId Encoded Encoded
+   . (HyperView id es, ToJSON (ViewState id), FromJSON (ViewState id), Hyperbole :> es)
+  => Event TargetViewId Encoded Value
   -> (Action id -> Eff (Reader id : State (ViewState id) : es) (View id ()))
   -> Eff es (Maybe Response)
 runHandler rawEvent run = do
@@ -79,10 +81,21 @@
 
 
 -- despite not needing any effects, this must be in Eff es to get `es` on the RHS
-decodeEvent :: forall id es. (HyperView id es, FromEncoded (ViewState id)) => Event TargetViewId Encoded Encoded -> Eff es (Maybe (Event id (Action id) (ViewState id)))
+decodeEvent :: forall id es. (HyperView id es, FromJSON (ViewState id), Hyperbole :> es) => Event TargetViewId Encoded Value -> Eff es (Maybe (Event id (Action id) (ViewState id)))
 decodeEvent (Event (TargetViewId ti) eact est) =
-  pure $ either (const Nothing) Just $ do
-    vid <- parseViewId ti
-    act <- parseAction eact
-    st <- parseEncoded est
-    pure $ Event vid act st
+  -- First, parse the view id. These should be unique on each page
+  case parseViewId ti of
+    Left _ -> pure Nothing
+    Right vid -> do
+      -- Then if parsing the state or action fails, throw an erro
+      either parseError (pure . Just) $ do
+        act <- parseAction eact
+        st <- parseState est
+        pure $ Event vid act st
+
+
+parseState :: (FromJSON a) => Value -> Either String a
+parseState v =
+  case A.fromJSON v of
+    A.Success a -> pure a
+    A.Error e -> Left e
diff --git a/src/Web/Hyperbole/Server/Message.hs b/src/Web/Hyperbole/Server/Message.hs
--- a/src/Web/Hyperbole/Server/Message.hs
+++ b/src/Web/Hyperbole/Server/Message.hs
@@ -4,8 +4,11 @@
 
 import Control.Applicative ((<|>))
 import Control.Exception (Exception)
+import Data.Aeson (Value (Null))
 import Data.Aeson qualified as Aeson
-import Data.Attoparsec.Text (Parser, char, endOfLine, isEndOfLine, parseOnly, sepBy, string, takeText, takeTill, takeWhile1)
+import Data.Aeson.Parser (json)
+import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfLine, isEndOfLine, parseOnly, sepBy, string, takeLazyByteString, takeWhile1)
+import Data.Attoparsec.ByteString.Lazy (takeTill)
 import Data.ByteString qualified as BS
 import Data.ByteString.Lazy qualified as BL
 import Data.List qualified as L
@@ -39,7 +42,7 @@
 
 data Message = Message
   { messageType :: Text
-  , event :: Event TargetViewId Encoded Encoded
+  , event :: Event TargetViewId Encoded Value
   , requestId :: RequestId
   , metadata :: Metadata
   , body :: MessageBody
@@ -64,7 +67,7 @@
 mimeType = "application/hyperbole.message"
 
 
-parseActionMessage :: Text -> Either String Message
+parseActionMessage :: BS.ByteString -> Either String Message
 parseActionMessage = parseOnly parser
  where
   parser :: Parser Message
@@ -82,17 +85,17 @@
     t <- takeWhile1 (/= '|')
     _ <- char '|'
     endOfLine
-    pure t
+    pure $ cs t
 
   body :: Parser MessageBody
   body = do
-    MessageBody . cs . T.strip <$> takeText
+    MessageBody . cs . T.strip . cs <$> takeLazyByteString
 
-  event :: Parser (Event TargetViewId Encoded Encoded)
+  event :: Parser (Event TargetViewId Encoded Value)
   event = do
     vid <- targetViewId
     act <- encodedAction
-    st <- encodedState <|> pure mempty
+    st <- encodedState <|> pure Null
     pure $ Event vid act st
    where
     targetViewId :: Parser TargetViewId
@@ -115,13 +118,10 @@
       endOfLine
       pure v
 
-    encodedState :: Parser Encoded
+    encodedState :: Parser Value
     encodedState = do
       _ <- string "State: "
-      inp <- takeLine
-      v <- case encodedParseText inp of
-        Left e -> fail $ "Parse Encoded ViewState failed: " <> cs e <> " from " <> cs inp
-        Right a -> pure a
+      v <- json
       endOfLine
       pure v
 
@@ -142,11 +142,11 @@
   metaKey = do
     key <- takeWhile1 (/= ':')
     _ <- string ": "
-    pure key
+    pure $ cs key
 
   takeLine :: Parser Text
   takeLine = do
-    takeTill isEndOfLine
+    cs <$> takeTill isEndOfLine
 
 
 -- Render ---------------------------------------------
@@ -192,7 +192,7 @@
   metaRequestId (RequestId reqId) =
     metadata "RequestId" (cs reqId)
 
-  eventMetadata :: Event TargetViewId Encoded Encoded -> Metadata
+  eventMetadata :: Event TargetViewId Encoded state -> Metadata
   eventMetadata event =
     Metadata
       [ ("ViewId", encodedToText event.viewId.encoded)
diff --git a/src/Web/Hyperbole/Server/Options.hs b/src/Web/Hyperbole/Server/Options.hs
--- a/src/Web/Hyperbole/Server/Options.hs
+++ b/src/Web/Hyperbole/Server/Options.hs
@@ -1,11 +1,19 @@
 {-# LANGUAGE LambdaCase #-}
 
-module Web.Hyperbole.Server.Options where
+module Web.Hyperbole.Server.Options
+  ( ServerOptions (..)
+  , defaultErrorMessage
+  , defaultErrorBody
+  , defaultError
+  , defaultParseRequestBodyOptions
+  ) where
 
+import Data.Aeson (Value)
 import Data.ByteString.Lazy qualified as BL
 import Data.String.Conversions (cs)
 import Data.Text (Text)
 import Data.Text qualified as T
+import Network.Wai.Parse as Wai (ParseRequestBodyOptions, defaultParseRequestBodyOptions)
 import Web.Atomic.CSS
 import Web.Hyperbole.Data.Encoded (Encoded, encodedToText)
 import Web.Hyperbole.Types.Event
@@ -16,6 +24,7 @@
 data ServerOptions = ServerOptions
   { toDocument :: BL.ByteString -> BL.ByteString
   , serverError :: ResponseError -> ServerError
+  , parseRequestBody :: ParseRequestBodyOptions
   }
 
 
@@ -44,7 +53,7 @@
     let msg = defaultErrorMessage err
      in ServerError msg (defaultErrorBody msg)
  where
-  errNotHandled :: Event TargetViewId Encoded Encoded -> ServerError
+  errNotHandled :: Event TargetViewId Encoded Value -> ServerError
   errNotHandled ev =
     ServerError "Action Not Handled" $ Body $ renderLazyByteString $ do
       el $ do
diff --git a/src/Web/Hyperbole/Server/Socket.hs b/src/Web/Hyperbole/Server/Socket.hs
--- a/src/Web/Hyperbole/Server/Socket.hs
+++ b/src/Web/Hyperbole/Server/Socket.hs
@@ -2,7 +2,7 @@
 
 module Web.Hyperbole.Server.Socket where
 
-import Control.Monad (void)
+import Control.Monad (void, when)
 import Data.Aeson (Value)
 import Data.Bifunctor (first)
 import Data.List qualified as L
@@ -14,7 +14,7 @@
 import Data.Text (Text)
 import Effectful
 import Effectful.Concurrent.Async
-import Effectful.Concurrent.STM (TVar, atomically, modifyTVar, readTVar, writeTVar)
+import Effectful.Concurrent.STM (STM, TVar, atomically, modifyTVar, readTVar, writeTVar)
 import Effectful.Dispatch.Dynamic
 import Effectful.Error.Static (throwError_)
 import Effectful.Exception
@@ -41,7 +41,7 @@
   }
 
 
-type RunningActions = Map TargetViewId (Encoded, Async ())
+type RunningActions = Map TargetViewId (Encoded, RequestId, Async ())
 
 
 runHyperboleSocket
@@ -82,12 +82,11 @@
     req <- parseMessageRequest msg
 
     a <- async $ do
-      -- is one already running?
       res <- trySync $ runHyperboleSocket opts conn req eff
       case res of
         -- TODO: catch socket errors separately from SomeException?
         Left (ex :: SomeException) -> do
-          -- It's not safe to send any exception over the wire
+          -- It's not safe to send SomeException over the wire
           -- log it to the console and send the error to the client
           liftIO $ print ex
           res2 <- trySync $ sendError conn (requestMetadata req) (opts.serverError ErrInternal)
@@ -108,28 +107,45 @@
       _ <- waitCatch a
       clearRunningAction req.requestId req.event
  where
-  addRunningAction :: (IOE :> es, Concurrent :> es) => Async () -> RequestId -> Maybe (Event TargetViewId Encoded Encoded) -> Eff es ()
-  addRunningAction a (RequestId reqId) = \case
+  addRunningAction :: (IOE :> es, Concurrent :> es) => Async () -> RequestId -> Maybe (Event TargetViewId Encoded Value) -> Eff es ()
+  addRunningAction a reqId = \case
     Nothing -> pure ()
     Just (Event vid act _) -> do
       -- liftIO $ putStrLn $ " [add] (" <> cs reqId <> ") " <> cs clientId.value <> "|" <> show vid
       maold <- atomically $ do
+        -- NOTE: actions is unique per-client, which is why it doesn't require a client id
         m <- readTVar @RunningActions actions
-        writeTVar actions $ M.insert vid (act, a) m
+        writeTVar actions $ M.insert vid (act, reqId, a) m
         pure $ M.lookup vid m
       case maold of
-        Nothing -> pure ()
-        Just (actold, aold) -> do
-          liftIO $ putStrLn $ "CANCEL (" <> cs reqId <> ") " <> cs (encodedToText vid.encoded) <> ": " <> cs (encodedToText actold)
+        Nothing -> do
+          -- liftIO $ putStrLn $ "nocancel " <> show vid
+          pure ()
+        Just (actold, RequestId rold, aold) -> do
+          liftIO $ putStrLn $ "CANCEL (" <> cs rold <> ") " <> cs (encodedToText vid.encoded) <> ": " <> cs (encodedToText actold)
           cancel aold
 
-  clearRunningAction :: (IOE :> es, Concurrent :> es) => RequestId -> Maybe (Event TargetViewId Encoded Encoded) -> Eff es ()
-  clearRunningAction (RequestId _) = \case
+  clearRunningAction :: (IOE :> es, Concurrent :> es) => RequestId -> Maybe (Event TargetViewId Encoded Value) -> Eff es ()
+  clearRunningAction reqId = \case
     Nothing -> pure ()
-    Just (Event vid _ _) -> do
-      _ <- atomically $ modifyTVar actions $ M.delete vid
-      pure ()
+    Just (Event vid _ _) -> atomically $ do
+      active <- isActiveRequestId vid reqId
+      when active $ do
+        _ <- modifyTVar actions $ M.delete vid
+        pure ()
 
+  activeRequestId :: TargetViewId -> STM (Maybe RequestId)
+  activeRequestId vid = do
+    m <- readTVar actions
+    pure $ do
+      (_, r, _) <- M.lookup vid m
+      pure r
+
+  isActiveRequestId :: TargetViewId -> RequestId -> STM Bool
+  isActiveRequestId vid reqId = do
+    r <- activeRequestId vid
+    pure $ r == Just reqId
+
   onMessageError :: (IOE :> es) => MessageError -> Eff es a
   onMessageError e = do
     liftIO $ do
@@ -139,7 +155,7 @@
   receiveMessage :: (IOE :> es) => Eff es Message
   receiveMessage = do
     t <- receiveText conn
-    case parseActionMessage t of
+    case parseActionMessage (cs t) of
       Left e -> throwIO $ InvalidMessage e t
       Right msg -> pure msg
 
@@ -161,7 +177,8 @@
         headers = Wai.requestHeaders wreq
         method = "POST"
 
-        body = msg.body.value
+    -- Parse params as url encoded, ignore files
+    let body = cs msg.body.value
 
     query <- HTTP.parseQuery . cs <$> requireMeta "Query" msg.metadata
     cookie <- cs <$> requireMeta "Cookie" msg.metadata
@@ -174,7 +191,8 @@
         , event = Just msg.event
         , host
         , query
-        , body
+        , form = Form mempty mempty
+        , input = body
         , method
         , cookies
         , requestId = msg.requestId
diff --git a/src/Web/Hyperbole/Server/Uploads.hs b/src/Web/Hyperbole/Server/Uploads.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Hyperbole/Server/Uploads.hs
@@ -0,0 +1,34 @@
+module Web.Hyperbole.Server.Uploads where
+
+import Control.Monad.Trans.Resource (InternalState, closeInternalState, createInternalState)
+import Data.String.Conversions (cs)
+import Effectful
+import Effectful.Exception (bracket)
+import Network.Wai qualified as Wai
+import Network.Wai.Parse (File, FileInfo (..), ParseRequestBodyOptions)
+import Network.Wai.Parse qualified as Wai
+import Web.Hyperbole.Types.Request (FileParam, Param, UploadedFile (..))
+
+
+withPostBody :: (IOE :> es) => ParseRequestBodyOptions -> Wai.Request -> ([Param] -> [FileParam] -> Eff es a) -> Eff es a
+withPostBody options req action = withUploadState $ \state -> do
+  (params, files) <- acceptFileUploads state
+  action params $ fmap fileParam files
+ where
+  withUploadState :: (IOE :> es) => (InternalState -> Eff es a) -> Eff es a
+  withUploadState = bracket createInternalState closeInternalState
+
+  acceptFileUploads :: (IOE :> es) => InternalState -> Eff es ([Param], [File FilePath])
+  acceptFileUploads state = do
+    liftIO $ Wai.parseRequestBodyEx options (Wai.tempFileBackEnd state) req
+
+  fileParam :: Wai.File FilePath -> FileParam
+  fileParam (key, f) = (key, fileInfo f)
+
+  fileInfo :: Wai.FileInfo FilePath -> UploadedFile
+  fileInfo FileInfo{fileName, fileContentType, fileContent} =
+    UploadedFile
+      { filePath = fileContent
+      , fileName = cs fileName
+      , contentType = cs fileContentType
+      }
diff --git a/src/Web/Hyperbole/Server/Wai.hs b/src/Web/Hyperbole/Server/Wai.hs
--- a/src/Web/Hyperbole/Server/Wai.hs
+++ b/src/Web/Hyperbole/Server/Wai.hs
@@ -3,6 +3,8 @@
 
 module Web.Hyperbole.Server.Wai where
 
+import Data.Aeson (Value)
+import Data.Aeson qualified as A
 import Data.Bifunctor (first)
 import Data.ByteString qualified as BS
 import Data.ByteString.Lazy qualified as BL
@@ -28,6 +30,7 @@
 import Web.Hyperbole.Effect.Hyperbole
 import Web.Hyperbole.Server.Message
 import Web.Hyperbole.Server.Options
+import Web.Hyperbole.Server.Uploads (withPostBody)
 import Web.Hyperbole.Types.Client
 import Web.Hyperbole.Types.Event (Event (..), TargetViewId (..))
 import Web.Hyperbole.Types.Request
@@ -43,17 +46,19 @@
   -> Eff (Hyperbole : es) Response
   -> Eff es Wai.ResponseReceived
 handleRequestWai options req respond actions = do
-  -- NOTE: Remember, this is called for both updates AND for page loads
-  body <- liftIO $ Wai.consumeRequestBodyLazy req
-  rq <- either throwIO pure $ do
-    fromWaiRequest req body
-  (res, client, rmts) <- runHyperboleWai rq actions
-  liftIO $ sendResponse options rq client res rmts respond
+  -- NOTE: Remember, this is called for both updates AND for page loads. We don't have to worry about anything the socket connection is doing
+  -- in particular, the socket connection sends a plain text body for input.value, but we don't care here
+  withPostBody options.parseRequestBody req $ \params files -> do
+    rq <- either throwIO pure $ do
+      fromWaiRequest req (Form params files)
+    (res, client, rmts) <- runHyperboleWai rq actions
+    liftIO $ sendResponse options rq client res rmts respond
 
 
 -- | Run the 'Hyperbole' effect to get a response
 runHyperboleWai
-  :: Request
+  :: (IOE :> es)
+  => Request
   -> Eff (Hyperbole : es) Response
   -> Eff es (Response, Client, [Remote])
 runHyperboleWai req = reinterpret (runHyperboleLocal req) $ \_ -> \case
@@ -85,7 +90,8 @@
   response :: Metadata -> Response -> Wai.Response
   response metas = \case
     (Err err) ->
-      respondError (errStatus err) [] $ options.serverError err
+      -- don't send headers
+      respondError (errStatus err) [] metas $ options.serverError err
     (Response (ViewUpdate _ vw)) -> do
       respondHtml status200 (clientHeaders client) $ renderViewResponse metas vw
     (Redirect u) -> do
@@ -114,7 +120,7 @@
   renderViewResponse metas (Body body) =
     addDocument $ renderLazyByteString (runViewContext metas () $ scriptMeta metas) <> "\n\n" <> body
 
-  respondError s hs serr = respondHtml s hs $ renderViewResponse (metaError serr.message) serr.body
+  respondError s hs metas serr = respondHtml s hs $ renderViewResponse (metaError serr.message <> metas) serr.body
   respondHtml s hs = Wai.responseLBS s (contentType ContentHtml : hs)
   -- respondText s hs = Wai.responseLBS s (contentType ContentText : hs)
 
@@ -142,8 +148,8 @@
   first (\e -> InvalidMessage e (cs inp)) $ parseActionMessage (cs inp)
 
 
-fromWaiRequest :: Wai.Request -> BL.ByteString -> Either MessageError Request
-fromWaiRequest wr body = do
+fromWaiRequest :: Wai.Request -> Form -> Either MessageError Request
+fromWaiRequest wr form = do
   let pth = path $ cs $ Wai.rawPathInfo wr
       query = Wai.queryString wr
       headers = Wai.requestHeaders wr
@@ -156,24 +162,25 @@
 
   pure $
     Request
-      { body = body
-      , path = pth
+      { path = pth
       , event
       , query
       , method
       , cookies
       , host
+      , form
+      , input = ""
       , requestId
       }
  where
-  lookupEvent :: [Header] -> Maybe (Event TargetViewId Encoded Encoded)
+  lookupEvent :: [Header] -> Maybe (Event TargetViewId Encoded Value)
   lookupEvent headers = do
     viewIdText <- cs <$> L.lookup "Hyp-ViewId" headers
     actText <- cs <$> L.lookup "Hyp-Action" headers
     stText <- cs <$> L.lookup "Hyp-State" headers
     act <- decode actText
     viewId <- TargetViewId <$> decode viewIdText
-    st <- decode stText
+    st <- A.decode stText
     pure $ Event viewId act st
 
 
diff --git a/src/Web/Hyperbole/Types/Request.hs b/src/Web/Hyperbole/Types/Request.hs
--- a/src/Web/Hyperbole/Types/Request.hs
+++ b/src/Web/Hyperbole/Types/Request.hs
@@ -1,9 +1,22 @@
-module Web.Hyperbole.Types.Request where
+module Web.Hyperbole.Types.Request
+  ( Request (..)
+  , Host (..)
+  , Form (..)
+  , FormParam (..)
+  , Param
+  , FileParam
+  , File
+  , RequestId (..)
+  , UploadedFile (..)
+  ) where
 
+import Data.Aeson (Value)
 import Data.ByteString qualified as BS
-import Data.ByteString.Lazy qualified as BL
+import Data.String (IsString (..))
+import Data.String.Conversions (cs)
 import Data.Text (Text)
 import Network.HTTP.Types (Method)
+import Network.Wai.Parse (File, Param)
 import Web.Hyperbole.Data.Cookie (Cookies)
 import Web.Hyperbole.Data.Encoded
 import Web.Hyperbole.Data.URI (Path, Query)
@@ -14,18 +27,46 @@
   deriving (Show)
 
 
+data UploadedFile = UploadedFile
+  { filePath :: FilePath
+  , fileName :: Text -- the original file name
+  , contentType :: Text -- the mime type
+  }
+  deriving (Show, Eq)
+
+
+type FileParam = (BS.ByteString, UploadedFile)
+
+
 data Request = Request
   { host :: Host
   , path :: Path
   , query :: Query
-  , body :: BL.ByteString
   , method :: Method
   , cookies :: Cookies
-  , event :: Maybe (Event TargetViewId Encoded Encoded)
+  , event :: Maybe (Event TargetViewId Encoded Value)
   , requestId :: RequestId
+  , form :: Form
+  , input :: Text
   }
   deriving (Show)
 
 
-newtype RequestId = RequestId Text
+data Form = Form
+  { params :: [Param]
+  , files :: [FileParam]
+  }
   deriving (Show)
+
+
+data FormParam
+  = FormParam Text
+  | FileParam UploadedFile
+  deriving (Show, Eq)
+instance IsString FormParam where
+  fromString s = FormParam (cs s)
+
+
+newtype RequestId = RequestId Text
+  deriving (Show, Eq)
+
diff --git a/src/Web/Hyperbole/Types/Response.hs b/src/Web/Hyperbole/Types/Response.hs
--- a/src/Web/Hyperbole/Types/Response.hs
+++ b/src/Web/Hyperbole/Types/Response.hs
@@ -2,6 +2,7 @@
 
 module Web.Hyperbole.Types.Response where
 
+import Data.Aeson (Value)
 import Data.ByteString.Lazy qualified as BL
 import Data.String (IsString (..))
 import Data.String.Conversions (cs)
@@ -32,7 +33,7 @@
   | ErrServer Text
   | ErrCustom ServerError
   | ErrInternal
-  | ErrNotHandled (Event TargetViewId Encoded Encoded)
+  | ErrNotHandled (Event TargetViewId Encoded Value)
   | ErrAuth Text
 instance Show ResponseError where
   show = \case
diff --git a/src/Web/Hyperbole/View/Types.hs b/src/Web/Hyperbole/View/Types.hs
--- a/src/Web/Hyperbole/View/Types.hs
+++ b/src/Web/Hyperbole/View/Types.hs
@@ -1,7 +1,9 @@
 {-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE UndecidableInstances #-}
 
 module Web.Hyperbole.View.Types where
 
+import Data.Aeson (FromJSON, ToJSON)
 import Data.String (IsString (..))
 import Data.Text (Text, pack)
 import Effectful
@@ -11,8 +13,7 @@
 import Web.Atomic.Html (Html (..))
 import Web.Atomic.Html qualified as Atomic
 import Web.Atomic.Types
-import Web.Hyperbole.Data.Encoded (decodeEither, encodedToText)
-import Web.Hyperbole.Data.Param (FromParam, ToParam (..))
+import Web.Hyperbole.Data.Encoded
 import Web.Hyperbole.View.ViewId
 
 
@@ -69,7 +70,7 @@
 
 newtype ChildView a = ChildView a
   deriving (Generic)
-instance (ViewId a, FromParam a, ToParam a) => ViewId (ChildView a) where
+instance (ViewId a, ToJSON a, FromJSON a) => ViewId (ChildView a) where
   type ViewState (ChildView a) = ViewState a
 
 
diff --git a/src/Web/Hyperbole/View/ViewId.hs b/src/Web/Hyperbole/View/ViewId.hs
--- a/src/Web/Hyperbole/View/ViewId.hs
+++ b/src/Web/Hyperbole/View/ViewId.hs
@@ -14,7 +14,10 @@
 @
 -}
 class ViewId a where
+  -- | Most hyperviews are stateless, but you can track internal state by setting this to something other than `()`
   type ViewState a :: Type
+
+
   type ViewState a = ()
 
 
diff --git a/test/Test/ArgumentSpec.hs b/test/Test/ArgumentSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/ArgumentSpec.hs
@@ -0,0 +1,121 @@
+module Test.ArgumentSpec where
+
+import Data.Aeson as A
+import Data.String.Conversions (cs)
+import Data.Text (Text)
+import GHC.Generics
+import Skeletest
+import Web.Hyperbole.Data.Argument
+import Web.Hyperbole.HyperView.Input
+
+
+spec :: Spec
+spec = do
+  argumentSpec
+  inputSpec
+
+
+data Tag = A | B
+  deriving (Generic, ToJSON, FromJSON, Eq, Show)
+
+
+data Tag2 = T2 | Tag Text
+  deriving (Generic, ToJSON, FromJSON, Eq, Show)
+
+
+data Record = Record
+  { age :: Int
+  , msg :: Text
+  }
+  deriving (Generic, ToJSON, FromJSON, Eq)
+
+
+argumentSpec :: Spec
+argumentSpec = do
+  describe "ToArgument" $ do
+    it "encodes basics as JSON" $ do
+      encodeArgument @Text "hello" `shouldBe` "\"hello\""
+      encodeArgument @Int 23 `shouldBe` "23"
+
+    it "encodes simple constructors raw" $ do
+      encodeArgument A `shouldBe` "A"
+      encodeArgument B `shouldBe` "B"
+
+    it "encodes Maybe as JSON" $ do
+      encodeArgument @(Maybe Int) Nothing `shouldBe` "null"
+      encodeArgument @(Maybe Int) (Just 23) `shouldBe` "23"
+      encodeArgument @(Maybe Tag) (Just A) `shouldBe` "A"
+      encodeArgument @(Maybe Tag) Nothing `shouldBe` "null"
+
+    it "encodes complex constructors as products" $ do
+      encodeArgument T2 `shouldBe` "(T2)"
+      encodeArgument (Tag "hello world") `shouldBe` "(Tag \"hello world\")"
+
+    -- it "should encode lists with spaces = plusses" $ do
+    --   encodeArgument @[Int] [1, 2, 3] `shouldBe` ParamValue ("1+2+3")
+    --   encodeArgument @[Text] ["one", "two"] `shouldBe` ParamValue ("one+two")
+    --   encodeArgument @[Text] ["hello world", "friend"] `shouldBe` ParamValue ("hello%20world+friend")
+
+    it "should not escape text" $ do
+      encodeArgument @Text "hello world" `shouldBe` "\"hello world\""
+      encodeArgument @Text "hello_world" `shouldBe` "\"hello_world\""
+      encodeArgument @Text "hello+world" `shouldBe` "\"hello+world\""
+
+    it "encodes json" $ do
+      let r = Record 10 "hello world"
+      encodeArgument r `shouldBe` cs (A.encode r)
+
+      let r2 = Record 10 "hello_world"
+      encodeArgument r2 `shouldBe` cs (A.encode r2)
+      encodeArgument r2 `shouldBe` cs (A.encode r2)
+
+  describe "FromArgument" $ do
+    it "parses basics" $ do
+      decodeArgument @Text "hello" `shouldBe` Right "hello"
+      decodeArgument @Int "3" `shouldBe` Right 3
+
+    it "decodes json" $ do
+      let r2 = Record 10 "hello_world"
+      decodeArgument (cs $ A.encode r2) `shouldBe` Right r2
+
+    it "can decode numbers as text" $ do
+      decodeArgument @Text "\"30\"" `shouldBe` Right "30"
+
+    it "should not escape text" $ do
+      decodeArgument @Text "\"hello world\"" `shouldBe` Right "hello world"
+      decodeArgument @Text "\"hello_world\"" `shouldBe` Right "hello_world"
+      decodeArgument @Text "\"hello+world\"" `shouldBe` Right "hello+world"
+
+  describe "RoundTrip" $ do
+    it "round trips constructors" $ do
+      decodeArgument (encodeArgument A) `shouldBe` Right A
+      decodeArgument (encodeArgument B) `shouldBe` Right B
+      decodeArgument (encodeArgument T2) `shouldBe` Right T2
+      let t = Tag "woo hoo"
+      decodeArgument (encodeArgument t) `shouldBe` Right t
+
+    it "round trips Maybe" $ do
+      decodeArgument @(Maybe Int) (encodeArgument @(Maybe Int) Nothing) `shouldBe` Right Nothing
+      decodeArgument @(Maybe Int) (encodeArgument @(Maybe Int) (Just 23)) `shouldBe` Right (Just 23)
+
+    it "round trips Maybe Product" $ do
+      decodeArgument @(Maybe Tag) (encodeArgument @(Maybe Tag) Nothing) `shouldBe` Right Nothing
+      decodeArgument @(Maybe Tag) (encodeArgument @(Maybe Tag) (Just A)) `shouldBe` Right (Just A)
+
+
+data LanguageFamily
+  = Functional
+  | ObjectOriented
+  deriving (Eq, Show, ToJSON, FromJSON, Ord, Generic, InputValue)
+
+
+inputSpec :: Spec
+inputSpec = do
+  describe "InputValue" $ do
+    it "encodes Maybe option" $ do
+      encodeArgument (Just ObjectOriented) `shouldBe` "ObjectOriented"
+
+    it "round trips Maybe option" $ do
+      let enc = encodeArgument @(Maybe LanguageFamily) Nothing
+      parseInputValue "ObjectOriented" `shouldBe` Right (Just ObjectOriented)
+      parseInputValue @(Maybe LanguageFamily) enc `shouldBe` Right Nothing
diff --git a/test/Test/EncodedSpec.hs b/test/Test/EncodedSpec.hs
--- a/test/Test/EncodedSpec.hs
+++ b/test/Test/EncodedSpec.hs
@@ -6,8 +6,8 @@
 import Data.Text (Text)
 import GHC.Generics (Generic)
 import Skeletest
+import Web.Hyperbole.Data.Argument
 import Web.Hyperbole.Data.Encoded
-import Web.Hyperbole.Data.Param
 
 
 -- TEST: QueryData underscores vs spaces
@@ -15,25 +15,24 @@
 data One = One
   -- toJSON automatically delegates to the child's ToJSON instance
   -- when it ought to be enought to delegate to the Generic instance!
-  deriving (Generic, Eq, ToEncoded, FromEncoded, ToParam, FromParam)
+  deriving (Generic, Eq, ToEncoded, FromEncoded, ToJSON, FromJSON)
 
 
 data Tag = A | B | C | D
-  deriving (Generic, Eq, ToEncoded, ToParam, FromParam)
+  deriving (Generic, Eq, ToEncoded, ToJSON, FromJSON)
 
 
 data Two = Two | Two2 Int
   deriving (Generic, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded)
 
 
--- Custom Param Encoding
-instance ToParam Two where
-  toParam Two = "Two"
-  toParam other = genericToParam other
-instance FromParam Two where
-  parseParam "Two" = pure Two
-  parseParam other = genericParseParam other
-
+-- -- Custom Param Encoding
+-- instance ToParam Two where
+--   toParam Two = "Two"
+--   toParam other = genericToParam other
+-- instance FromParam Two where
+--   parseParam "Two" = pure Two
+--   parseParam other = genericParseParam other
 
 data Sum
   = Sumthing
@@ -48,11 +47,19 @@
 data Nested
   = Gogo One
   | RecordN Record
+  | RecordM RecordMaybe
   | RecordEx Record Int
   | Tag Tag
+  | NProd SubProduct
   deriving (Generic, ToEncoded, FromEncoded, Eq)
 
 
+data SubProduct
+  = MoreThanOne Int
+  | AnotherOne Text Int
+  deriving (Generic, Eq, ToJSON, FromJSON)
+
+
 data Product
   = Product Text Int Bool
   deriving (Generic, Eq, ToEncoded, FromEncoded)
@@ -62,9 +69,16 @@
   { one :: Int
   , two :: Text
   }
-  deriving (Generic, Show, ToJSON, FromJSON, Eq, ToEncoded, FromEncoded, ToParam, FromParam)
+  deriving (Generic, Show, ToJSON, FromJSON, Eq, ToEncoded, FromEncoded)
 
 
+data RecordMaybe = RecordMaybe
+  { one :: Int
+  , two :: Maybe Text
+  }
+  deriving (Generic, Show, ToJSON, FromJSON, Eq, ToEncoded, FromEncoded)
+
+
 data Product4 = Product4 Text Text Text Text deriving (Generic, Show, Eq, Read, FromEncoded, ToEncoded)
 
 
@@ -76,38 +90,37 @@
 
     it "should encode multi tags" $ do
       genericToEncoded Two `shouldBe` Encoded "Two" []
-      genericToEncoded (Two2 3) `shouldBe` Encoded "Two2" [jsonParam $ Number 3]
-      genericToEncoded (Gogo One) `shouldBe` Encoded "Gogo" [toParam One]
+      genericToEncoded (Two2 3) `shouldBe` Encoded "Two2" [JSON $ Number 3]
+      genericToEncoded (Gogo One) `shouldBe` Encoded "Gogo" [toArgument One]
 
     it "should encode sum tags" $ do
-      genericToEncoded (CTwo Two) `shouldBe` Encoded "CTwo" [toParam Two]
+      genericToEncoded (CTwo Two) `shouldBe` Encoded "CTwo" [toArgument Two]
 
     it "basic" $ do
-      genericToEncoded (Gogo One) `shouldBe` Encoded "Gogo" [toParam One]
+      genericToEncoded (Gogo One) `shouldBe` Encoded "Gogo" [toArgument One]
 
     it "product" $ do
-      genericToEncoded (Product "one" 2 True) `shouldBe` Encoded "Product" [toParam @Text "one", toParam @Int 2, toParam True]
+      genericToEncoded (Product "one" 2 True) `shouldBe` Encoded "Product" [toArgument @Text "one", toArgument @Int 2, toArgument True]
 
     it "product4" $ do
       let prod = Product4 "one" "two" "three" "four"
-      genericToEncoded prod `shouldBe` Encoded "Product4" (fmap toParam ["one" :: Text, "two", "three", "four"])
+      genericToEncoded prod `shouldBe` Encoded "Product4" (fmap toArgument ["one" :: Text, "two", "three", "four"])
 
   describe "genericParseEncoded" $ do
     it "product4" $ do
-      genericParseEncoded (Encoded "Product4" (fmap toParam ["one" :: Text, "two", "three", "four"])) `shouldBe` Right (Product4 "one" "two" "three" "four")
+      genericParseEncoded (Encoded "Product4" (fmap toArgument ["one" :: Text, "two", "three", "four"])) `shouldBe` Right (Product4 "one" "two" "three" "four")
 
     it "sum" $ do
       genericParseEncoded @Sum (Encoded "Sumthing" []) `shouldBe` Right Sumthing
-      genericParseEncoded @Sum (Encoded "Num" [toParam @Int 2]) `shouldBe` Right (Num 2)
-      genericParseEncoded @Sum (Encoded "Str" [toParam @Text "OK"]) `shouldBe` Right (Str "OK")
+      genericParseEncoded @Sum (Encoded "Num" [toArgument @Int 2]) `shouldBe` Right (Num 2)
+      genericParseEncoded @Sum (Encoded "Str" [toArgument @Text "OK"]) `shouldBe` Right (Str "OK")
 
-      genericParseEncoded @Sum (Encoded "COne" [toParam One]) `shouldBe` Right (COne One)
-      genericParseEncoded @Sum (Encoded "CTwo" [toParam Two]) `shouldBe` Right (CTwo Two)
+      genericParseEncoded @Sum (Encoded "COne" [toArgument One]) `shouldBe` Right (COne One)
+      genericParseEncoded @Sum (Encoded "CTwo" [toArgument Two]) `shouldBe` Right (CTwo Two)
 
   describe "toEncoded" $ do
     it "encodes numbers as text" $ do
-      -- no, this is right, but when we go to decode, we pick up the json instance...
-      toEncoded (Num 1) `shouldBe` Encoded "Num" [jsonParam $ Number 1]
+      toEncoded (Num 1) `shouldBe` Encoded "Num" [toArgument @Int 1]
 
   describe "toText" $ do
     it "should encode single tags" $ do
@@ -118,29 +131,28 @@
       encodedParseText "One" `shouldBe` Right (Encoded "One" [])
 
     it "parses numbers" $ do
-      encodedParseText "Num 1" `shouldBe` Right (Encoded "Num" [jsonParam $ Number 1])
+      encodedParseText "Num 1" `shouldBe` Right (Encoded "Num" [JSON $ Number 1])
 
   describe "encode" $ do
     it "should encode single tags" $ do
       encode One `shouldBe` "One"
 
-    it "encodes strings" $ do
-      encode (Str "hello world") `shouldBe` "Str hello_world"
-      -- but then how is it going to know the difference between the two?
-      encode (Str " ") `shouldBe` "Str _"
-      encode (Str "") `shouldBe` "Str |"
-      encode (Str "_") `shouldBe` "Str \\_"
-      encode (Str "\n") `shouldBe` "Str \\n"
-      encode (Str "hello_world") `shouldBe` "Str hello\\_world"
-      encode (Str "hello+world") `shouldBe` "Str hello+world"
-      encode (Str "hello\nworld") `shouldBe` "Str hello\\nworld"
+    it "encodes strings as JSON" $ do
+      encode (Str "hello world") `shouldBe` "Str \"hello world\""
+      encode (Str " ") `shouldBe` "Str \" \""
+      encode (Str "") `shouldBe` "Str \"\""
+      encode (Str "_") `shouldBe` "Str \"_\""
+      encode (Str "\n") `shouldBe` "Str \"\\n\""
+      encode (Str "hello_world") `shouldBe` "Str \"hello_world\""
+      encode (Str "hello+world") `shouldBe` "Str \"hello+world\""
+      encode (Str "hello\nworld") `shouldBe` "Str \"hello\\nworld\""
 
     it "should encode records`" $ do
       -- no field names for ourselves
-      encode (Record 1 "two") `shouldBe` "Record 1 two"
+      encode (Record 1 "two") `shouldBe` "Record 1 \"two\""
       -- but if it is nested it uses the JSON instance, obviously
       let r2 = Record 1 "two"
-      encode (RecordN r2) `shouldBe` "RecordN " <> encodeParam (jsonParam r2)
+      encode (RecordN r2) `shouldBe` "RecordN " <> encodeFromArgument (JSON $ toJSON r2)
 
     it "no special case for nested constructors`" $ do
       encode A `shouldBe` "A"
@@ -148,106 +160,122 @@
 
     it "should encode sum" $ do
       encode (Num 1) `shouldBe` "Num 1"
-      encode (Str "hello world") `shouldBe` "Str hello_world"
+      encode (Str "hello world") `shouldBe` "Str \"hello world\""
 
-    it "should encode prodcuts" $ do
-      encode (Product "hello world" 2 True) `shouldBe` "Product hello_world 2 true"
+    it "should encode products" $ do
+      encode (Product "hello world" 2 True) `shouldBe` "Product \"hello world\" 2 true"
 
     it "encodes more constructors" $ do
-      encode (CTwo (Two2 3)) `shouldBe` "CTwo [\"Two2\",3]"
-      encode (CTwo Two) `shouldBe` "CTwo Two" -- uses the custom toparam instance
+      encode (CTwo (Two2 3)) `shouldBe` "CTwo (Two2 3)" -- uses custom tuple constructor
+      encode (CTwo Two) `shouldBe` "CTwo (Two)" -- uses the custom simpleTag
       encode (COne One) `shouldBe` "COne []"
 
+    it "encodes nested products" $ do
+      encode (NProd (AnotherOne "hi" 3)) `shouldBe` "NProd (AnotherOne \"hi\" 3)"
+
+    it "encodes nested records" $ do
+      encode (RecordN (Record 3 "hello")) `shouldBe` "RecordN {\"one\":3,\"two\":\"hello\"}"
+
+    it "encodes nested records with nothing as null" $ do
+      encode (RecordM (RecordMaybe 3 Nothing)) `shouldBe` "RecordM {\"one\":3,\"two\":null}"
+
   describe "decode" $ do
     it "should encode single tags" $ do
       decode "One" `shouldBe` Just One
 
     it "should decode nested sum" $ do
       decodeEither "Num 1" `shouldBe` Right (Num 1)
-      decodeEither "Str str" `shouldBe` Right (Str "str")
-      decodeEither "Str hello_world" `shouldBe` Right (Str "hello world")
+      decodeEither "Str \"str\"" `shouldBe` Right (Str "str")
+      decodeEither "Str \"hello world\"" `shouldBe` Right (Str "hello world")
 
     it "no special case for nested constructors`" $ do
-      decode "Tag A" `shouldBe` Just (Tag A)
+      decodeEither "Tag A" `shouldBe` Right (Tag A)
 
     it "decodes strings" $ do
-      decode "Str |" `shouldBe` pure (Str "")
+      decode "Str \"\"" `shouldBe` pure (Str "")
 
   describe "params" $ do
-    it "sanitizeText" $ do
-      encodeParam "hello world" `shouldBe` "hello_world"
-      encodeParam "hello_world" `shouldBe` "hello\\_world"
-      encodeParam "hello\nworld" `shouldBe` "hello\\nworld"
-
-    it "desanitizeText" $ do
-      decodeParam "hello_world" `shouldBe` "hello world"
-      decodeParam "hello\\_world" `shouldBe` "hello_world"
-      decodeParam "hello\\nworld" `shouldBe` "hello\nworld"
+    -- it "sanitizeText" $ do
+    --   encodeParam "hello world" `shouldBe` "hello_world"
+    --   encodeParam "hello_world" `shouldBe` "hello\\_world"
+    --   encodeParam "hello\nworld" `shouldBe` "hello\\nworld"
+    --
+    -- it "desanitizeText" $ do
+    --   decodeParam "hello_world" `shouldBe` "hello world"
+    --   decodeParam "hello\\_world" `shouldBe` "hello_world"
+    --   decodeParam "hello\\nworld" `shouldBe` "hello\nworld"
 
     -- TODO: Add more edge cases to check if "\n" is escaped properly.
-    it "edge cases" $ do
-      encodeParam "" `shouldBe` "|"
-      encodeParam " " `shouldBe` "_"
-      encodeParam "  " `shouldBe` "__"
+    -- it "edge cases" $ do
+    --   encodeParam "" `shouldBe` "\"\""
+    --   encodeParam " " `shouldBe` "\"_\""
+    --   encodeParam "  " `shouldBe` "\"__\""
+    --
+    --   encodeParam "_" `shouldBe` "\"\\_\""
+    --   encodeParam "__" `shouldBe` "\"\\_\\_\""
+    --
+    --   decodeParam "|" `shouldBe` ParamValue "|"
+    --   decodeParam "_" `shouldBe` ParamValue "_"
+    --   decodeParam "\\_" `shouldBe` "_"
+    --   decodeParam "\\_\\_" `shouldBe` "__"
 
-      encodeParam "_" `shouldBe` "\\_"
-      encodeParam "__" `shouldBe` "\\_\\_"
+    describe "round trip" $ do
+      it "records" $ do
+        let enc = genericToEncoded (Record 1 "two")
+        genericParseEncoded enc `shouldBe` Right (Record 1 "two")
 
-      decodeParam "|" `shouldBe` ""
-      decodeParam "_" `shouldBe` " "
-      decodeParam "\\_" `shouldBe` "_"
-      decodeParam "\\_\\_" `shouldBe` "__"
+      it "product" $ do
+        decode (encode (Product "hello world" 2 False)) `shouldBe` Just (Product "hello world" 2 False)
+        decode (encode (Product "bob" (-2) True)) `shouldBe` Just (Product "bob" (-2) True)
 
-  describe "round trip" $ do
-    it "records" $ do
-      let enc = genericToEncoded (Record 1 "two")
-      genericParseEncoded enc `shouldBe` Right (Record 1 "two")
+      it "nested product with records" $ do
+        let r = RecordEx (Record 2 "three") 33
+        let t = encode r
+        decode t `shouldBe` Just r
 
-    it "product" $ do
-      decode (encode (Product "hello world" 2 False)) `shouldBe` Just (Product "hello world" 2 False)
-      decode (encode (Product "bob" (-2) True)) `shouldBe` Just (Product "bob" (-2) True)
+      it "special case constructors" $ do
+        decodeEither (encode (CTwo Two)) `shouldBe` Right (CTwo Two)
+        decode (encode (Tag B)) `shouldBe` Just (Tag B)
 
-    it "nested product with records" $ do
-      let r = RecordEx (Record 2 "three") 33
-      let t = encode r
-      decode t `shouldBe` Just r
+      it "special case products" $ do
+        decodeEither (encode (CTwo (Two2 3))) `shouldBe` Right (CTwo (Two2 3))
+        decodeEither (encode (NProd (AnotherOne "HI" 3))) `shouldBe` Right (NProd (AnotherOne "HI" 3))
 
-    it "special case constructors" $ do
-      decode (encode (CTwo Two)) `shouldBe` Just (CTwo Two)
-      decode (encode (Tag B)) `shouldBe` Just (Tag B)
+      it "big product" $ do
+        let p = Product4 "hello world" "two_times" "three" "four"
+        decode (encode p) `shouldBe` Just p
 
-    it "big product" $ do
-      let p = Product4 "hello world" "two_times" "three" "four"
-      decode (encode p) `shouldBe` Just p
+      it "empty strings" $ do
+        decode (encode $ Str "") `shouldBe` Just (Str "")
 
-    it "empty strings" $ do
-      decode (encode $ Str "") `shouldBe` Just (Str "")
+      it "special characters" $ do
+        let str = "hello+world \"bob_lives\""
+        decode (encode $ Str str) `shouldBe` Just (Str str)
 
-    it "special characters" $ do
-      let str = "hello+world \"bob_lives\""
-      decode (encode $ Str str) `shouldBe` Just (Str str)
+      it "encodes lists`" $ do
+        let l = List ["hello, world", "", "+,|<[]"]
+        decode @Sum (encode l) `shouldBe` Just l
 
-    it "encodes lists`" $ do
-      let l = List ["hello, world", "", "+,|<[]"]
-      print $ encode l
-      decode @Sum (encode l) `shouldBe` Just l
+      it "encodes newlines in strings" $ do
+        let s = Str "hello\nworld"
+        decode @Sum (encode s) `shouldBe` Just s
 
-    -- Regression tests for https://github.com/seanhess/hyperbole/issues/187
-    -- A ViewId (or state) containing a list with newline characters must
-    -- encode/decode correctly.  Previously, desanitizeParamText blindly
-    -- replaced the JSON escape sequence "\\n" with a real newline, corrupting
-    -- the JSON and causing "No Handler for Event viewId".
-    it "list with newline character round-trips correctly (issue #187)" $ do
-      decode @Sum (encode (List ["\n"])) `shouldBe` Just (List ["\n"])
+      -- Regression tests for https://github.com/seanhess/hyperbole/issues/187
+      -- A ViewId (or state) containing a list with newline characters must
+      -- encode/decode correctly.  Previously, desanitizeParamText blindly
+      -- replaced the JSON escape sequence "\\n" with a real newline, corrupting
+      -- the JSON and causing "No Handler for Event viewId".
+      it "list with newline character round-trips correctly (issue #187)" $ do
+        decode @Sum (encode (List ["\n"])) `shouldBe` Just (List ["\n"])
 
-    it "list with newline in multiple elements" $ do
-      decode @Sum (encode (List ["\n", "hello\nworld", "plain"])) `shouldBe` Just (List ["\n", "hello\nworld", "plain"])
+      it "list with newline in multiple elements" $ do
+        decode @Sum (encode (List ["\n", "hello\nworld", "plain"])) `shouldBe` Just (List ["\n", "hello\nworld", "plain"])
 
-    it "strings" $ do
-      decode @Sum (encode (Str "")) `shouldBe` pure (Str "")
-      decode @Sum (encode (Str " ")) `shouldBe` pure (Str " ")
-      decode @Sum (encode (Str "_")) `shouldBe` pure (Str "_")
-      decode @Sum (encode (Str "~")) `shouldBe` pure (Str "~")
-      decode @Sum (encode (Str "+")) `shouldBe` pure (Str "+")
-      decode @Sum (encode (Str "hello world")) `shouldBe` pure (Str "hello world")
-      decode @Sum (encode (Str "hello_world")) `shouldBe` pure (Str "hello_world")
+      it "strings" $ do
+        decode @Sum (encode (Str "")) `shouldBe` pure (Str "")
+        decode @Sum (encode (Str " ")) `shouldBe` pure (Str " ")
+        decode @Sum (encode (Str "_")) `shouldBe` pure (Str "_")
+        decode @Sum (encode (Str "~")) `shouldBe` pure (Str "~")
+        decode @Sum (encode (Str "+")) `shouldBe` pure (Str "+")
+        decode @Sum (encode (Str "hello world")) `shouldBe` pure (Str "hello world")
+        decode @Sum (encode (Str "hello_world")) `shouldBe` pure (Str "hello_world")
diff --git a/test/Test/FormSpec.hs b/test/Test/FormSpec.hs
--- a/test/Test/FormSpec.hs
+++ b/test/Test/FormSpec.hs
@@ -1,11 +1,16 @@
 {-# LANGUAGE DerivingVia #-}
 {-# LANGUAGE OverloadedLists #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
 
 module Test.FormSpec where
 
+import Data.Aeson (FromJSON, ToJSON)
 import Data.Text (Text)
+import GHC.Exts (IsList (..))
 import Skeletest
 import Web.Hyperbole.HyperView.Forms
+import Web.Hyperbole.HyperView.Input (InputValue (..), encodeOption)
+import Web.Hyperbole.Types.Request
 
 
 data Example f = Example
@@ -33,17 +38,36 @@
   deriving (Generic, FromForm, Show, Eq)
 
 
+instance IsList Form where
+  type Item Form = Param
+  fromList ps = Form ps mempty
+  toList (Form ps _) = ps
+
+
 spec :: Spec
-spec = withMarkers ["param"] $ do
+spec = do
   describe "forms" $ do
-    it "should parse a form" $ do
-      case fromForm @(Example Identity) [("message", "hello"), ("age", "23"), ("whatever", "")] of
+    it "parses a record" $ do
+      case fromForm @(Example Identity) [("message", "hello"), ("age", "23"), ("whatever", "10.4"), ("maybeMessage", "hello world")] of
         Left e -> fail $ show e
         Right a -> do
           a.message `shouldBe` "hello"
           a.age `shouldBe` 23
-          a.whatever `shouldBe` Nothing
+          a.whatever `shouldBe` Just 10.4
+          a.maybeMessage `shouldBe` Just "hello world"
 
+    it "parses empty values" $ do
+      let e = fromForm @(Example Identity) [("age", ""), ("whatever", "")]
+      fmap (.age) e `shouldBe` Right 0
+      fmap (.whatever) e `shouldBe` Right Nothing
+      fmap (.maybeMessage) e `shouldBe` Right Nothing
+
+    it "parses missing values" $ do
+      let e = fromForm @(Example Identity) []
+      fmap (.age) e `shouldBe` Right 0
+      fmap (.whatever) e `shouldBe` Right Nothing
+      fmap (.maybeMessage) e `shouldBe` Right Nothing
+
     it "should parse a form with a number for the text" $ do
       let res = fromForm @(Example Identity) [("message", "30"), ("age", "0"), ("whatever", "2"), ("maybeMessage", "hello")]
       res `shouldBe` Right (Example "30" 0 (Just 2) (Just "hello"))
@@ -56,7 +80,7 @@
       let res = fromForm @(Example Identity) [("message", "30"), ("age", "0"), ("maybeMessage", "")]
       res `shouldBe` Right (Example "30" 0 Nothing (Just ""))
 
-    it "parses weird" $ do
+    it "parses bools" $ do
       fromForm @Flags [("a", "true"), ("b", "off")] `shouldBe` Right (Flags True False)
       fromForm @Flags [("a", "on"), ("b", "false")] `shouldBe` Right (Flags True False)
       fromForm @Flags [("a", "on")] `shouldBe` Right (Flags True False)
@@ -64,8 +88,32 @@
     it "parses missing bools as false" $ do
       fromForm @Flags [("a", "true")] `shouldBe` Right (Flags True False)
 
-    it "parses underscores" $ do
+    it "parses special characters" $ do
       fromForm @Todo [("msg", "test")] `shouldBe` Right (Todo "test")
       fromForm @Todo [("msg", "hello world")] `shouldBe` Right (Todo "hello world")
       fromForm @Todo [("msg", "hello+world")] `shouldBe` Right (Todo "hello+world")
       fromForm @Todo [("msg", "hello_world")] `shouldBe` Right (Todo "hello_world")
+
+  describe "options" $ do
+    it "decodes option as input" $ do
+      let enc = encodeOption (Twonit "Hello world" 33)
+      parseInputValue enc `shouldBe` Right (Twonit "Hello world" 33)
+
+    it "decodes option as field" $ do
+      let enc = encodeOption (Twonit "Hello world" 33)
+      fromField (Just (FormParam enc)) `shouldBe` Right (Twonit "Hello world" 33)
+
+    it "round trips options: Just" $ do
+      let enc1 = encodeOption (Just $ Onit "Hello world")
+      fromField (Just (FormParam enc1)) `shouldBe` Right (Onit "Hello world")
+
+    it "round trips options: Nothing" $ do
+      let enc2 = encodeOption @(Maybe FancySum) Nothing
+      fromField @(Maybe FancySum) (Just (FormParam enc2)) `shouldBe` Right Nothing
+
+
+data FancySum
+  = Unit
+  | Onit Text
+  | Twonit Text Int
+  deriving (Generic, Eq, FromJSON, ToJSON, InputValue, FromField)
diff --git a/test/Test/ParamSpec.hs b/test/Test/ParamSpec.hs
deleted file mode 100644
--- a/test/Test/ParamSpec.hs
+++ /dev/null
@@ -1,96 +0,0 @@
-{-# LANGUAGE OverloadedLists #-}
-
-module Test.ParamSpec where
-
-import Data.Aeson
-import Data.String.Conversions (cs)
-import Data.Text (Text)
-import GHC.Generics
-import Skeletest
-import Web.Hyperbole.Data.Param
-
-
-spec :: Spec
-spec = withMarkers ["param"] $ do
-  describe "param" paramSpec
-
-
-data Record = Record
-  { age :: Int
-  , msg :: Text
-  }
-  deriving (Generic, ToJSON, FromJSON, ToParam, FromParam, Eq)
-
-
-data Tag = A | B
-  deriving (Generic, ToParam, FromParam, Eq, Show)
-
-
-data Tag2 = C | Tag Text
-  deriving (Generic, ToParam, FromParam, Eq, Show)
-instance ToJSON Tag2 where
-  toJSON = genericToJSON jsonOptions
-
-
-paramSpec :: Spec
-paramSpec = do
-  describe "ToParam" $ do
-    it "should encode basics" $ do
-      toParam @Text "hello" `shouldBe` "hello"
-      toParam @Int 23 `shouldBe` ParamValue "23"
-
-    it "should encode Maybe" $ do
-      toParam @(Maybe Int) Nothing `shouldBe` ParamValue "~"
-      toParam @(Maybe Int) (Just 23) `shouldBe` ParamValue "23"
-
-    it "encodes simple constructors" $ do
-      toParam A `shouldBe` ParamValue "A"
-      toParam B `shouldBe` ParamValue "B"
-
-    it "encodes complex constructors as json" $ do
-      toParam C `shouldBe` jsonParam C
-      toParam (Tag "hello world") `shouldBe` jsonParam (Tag "hello world")
-
-    -- it "should encode lists with spaces = plusses" $ do
-    --   toParam @[Int] [1, 2, 3] `shouldBe` ParamValue ("1+2+3")
-    --   toParam @[Text] ["one", "two"] `shouldBe` ParamValue ("one+two")
-    --   toParam @[Text] ["hello world", "friend"] `shouldBe` ParamValue ("hello%20world+friend")
-
-    it "should not escape text" $ do
-      toParam @Text "hello world" `shouldBe` ParamValue "hello world"
-      toParam @Text "hello_world" `shouldBe` ParamValue "hello_world"
-      toParam @Text "hello+world" `shouldBe` ParamValue "hello+world"
-
-    it "encodes json" $ do
-      let r = Record 10 "hello world"
-      toParam r `shouldBe` jsonParam (toJSON r)
-
-      let r2 = Record 10 "hello_world"
-      toParam r2 `shouldBe` jsonParam (toJSON r2)
-      toParam r2 `shouldBe` ParamValue (cs (encode r2))
-
-  describe "FromParam" $ do
-    it "should parse basics" $ do
-      parseParam @Text "hello" `shouldBe` Right "hello"
-      parseParam @Int "3" `shouldBe` Right 3
-
-    it "decodes json" $ do
-      let r2 = Record 10 "hello_world"
-      parseParam (jsonParam r2) `shouldBe` Right r2
-      parseParam (ParamValue $ cs $ encode r2) `shouldBe` Right r2
-
-    it "can decode numbers as text" $ do
-      parseParam @Text "3" `shouldBe` Right "3"
-
-    it "should not escape text" $ do
-      parseParam @Text "hello world" `shouldBe` Right "hello world"
-      parseParam @Text "hello_world" `shouldBe` Right "hello_world"
-      parseParam @Text "hello+world" `shouldBe` Right "hello+world"
-
-  describe "RoundTrip" $ do
-    it "round trips constructors" $ do
-      parseParam (toParam A) `shouldBe` Right A
-      parseParam (toParam B) `shouldBe` Right B
-      parseParam (toParam C) `shouldBe` Right C
-      let t = Tag "woo hoo"
-      parseParam (toParam t) `shouldBe` Right t
diff --git a/test/Test/QuerySpec.hs b/test/Test/QuerySpec.hs
--- a/test/Test/QuerySpec.hs
+++ b/test/Test/QuerySpec.hs
@@ -27,7 +27,9 @@
   { age :: Int
   , msg :: Text
   }
-  deriving (Generic, ToJSON, FromJSON, ToParam, FromParam, Eq, FromQuery, ToQuery)
+  deriving (Generic, ToJSON, FromJSON, Eq, FromQuery, ToQuery)
+instance Default Record where
+  def = Record 0 ""
 
 
 classSpec :: Spec
@@ -43,12 +45,12 @@
 
   describe "ToQuery" $ do
     it "encodes record" $ do
-      let r = Record 20 "hello world"
-      QueryData.render (toQuery r) `shouldBe` "age=20&msg=hello_world"
+      let r = Record 20 "hello world_go+big"
+      QueryData.render (toQuery r) `shouldBe` "age=20&msg=hello+world_go%2Bbig"
 
   describe "roundtrip" $ do
     it "round trips" $ do
-      let r = Record 20 "hello world"
+      let r = Record 20 "hello world_go+big"
       parseQuery (toQuery r) `shouldBe` Right r
 
 
@@ -70,9 +72,12 @@
     let q = mempty & QueryData.insert @Text "msg" "bob&henry=fast"
     QueryData.render q `shouldBe` "msg=bob%26henry%3Dfast"
 
-  -- it "handles underscores" $ do
-  --   QueryData.render [(Param "msg", ParamValue "hello_world" $ String "hello_world")] `shouldBe` "msg=hello%5C_world"
-  --   QueryData.render [(Param "msg", ParamValue "hello world" $ String "hello world")] `shouldBe` "msg=hello_world"
+  it "should roundtrip spaces" $ do
+    let msg = "hello world"
+    let q = mempty & QueryData.insert @Text "msg" msg
+    let out = QueryData.render q
+    let q' = QueryData.parse out
+    QueryData.lookup "msg" q' `shouldBe` Just msg
 
   it "should roundtrip special characters" $ do
     let msg = "bob&henry=fast"
@@ -82,17 +87,12 @@
     QueryData.lookup "msg" q' `shouldBe` Just msg
 
 
--- it "should preserve plusses" $ do
---   let QueryData q = QueryData $ M.fromList [("items", "one+two")]
---   print $ HTTP.toQuery $ M.toList q
---   QueryData.render (QueryData q) `shouldBe` "items=one+two"
-
 data Filters = Filters
   { term :: Text
   , isActive :: Bool
   , another :: Maybe Text
   }
-  deriving (Eq, Show)
+  deriving (Generic, Eq, Show)
 
 
 instance ToQuery Filters where
@@ -111,41 +111,34 @@
     pure Filters{..}
 
 
-data Filters' = Filters'
-  { term :: Text
-  , isActive :: Bool
-  }
-  deriving (Generic, Eq, ToJSON, FromJSON, FromParam, ToParam)
-instance Default Filters' where
-  def = Filters' "" False
-
-
 data Nested = Nested
-  { filters :: Filters'
+  { record :: Record
+  , awesome :: Bool
   }
-  deriving (Generic, ToQuery, FromQuery)
+  deriving (Generic, ToQuery, FromQuery, Eq)
 
 
--- instance ToQuery Nested where
---   toQuery n =
---     mempty & QueryData.insert "filters" (JSON n.filters)
---
---
--- instance FromQuery Nested where
---   parseQuery q =
---     mempty & QueryData.insert "filters" (JSON n.filters)
-
 multiSpec :: Spec
 multiSpec = do
   describe "Roundtrip" $ do
     it "should parse from querydata" $ do
       let f = Filters "hello world" False Nothing
-      let out = QueryData.render (toQuery f)
-      let q = QueryData.parse out
-      parseQuery q `shouldBe` Right f
+      roundTrip f
 
     it "should work with Just" $ do
       let f = Filters "hello_world" False (Just "hello")
-      let out = QueryData.render (toQuery f)
-      let q = QueryData.parse out
-      parseQuery q `shouldBe` Right f
+      roundTrip f
+
+    it "integers, special characters, spaces" $ do
+      let r = Record 30 "hello world_this+is"
+      roundTrip r
+
+    it "nested JSON records" $ do
+      let r = Record 30 "hello world_this+is"
+      let n = Nested r True
+      roundTrip n
+ where
+  roundTrip val = do
+    let out = QueryData.render (toQuery val)
+    let q = QueryData.parse out
+    parseQuery q `shouldBe` Right val
diff --git a/test/Test/SessionSpec.hs b/test/Test/SessionSpec.hs
--- a/test/Test/SessionSpec.hs
+++ b/test/Test/SessionSpec.hs
@@ -2,13 +2,13 @@
 
 module Test.SessionSpec where
 
+import Data.Aeson qualified as A
 import Data.String.Conversions (cs)
 import Data.Text (Text)
 import Network.HTTP.Types (urlEncode)
 import Skeletest
 import Web.Hyperbole
 import Web.Hyperbole.Data.Cookie as Cookie
-import Web.Hyperbole.Data.Encoded qualified as Encoded
 import Web.Hyperbole.Data.URI
 import Web.Hyperbole.Effect.Session (sessionCookie)
 
@@ -16,13 +16,13 @@
 -- import Skeletest.Predicate qualified as P
 
 data Woot = Woot Text
-  deriving (Generic, Show, ToEncoded, FromEncoded)
+  deriving (Generic, Show, ToJSON, FromJSON)
 instance Session Woot where
   cookiePath = Just $ Path ["somepage"]
 
 
 data InsecureSession = InsecureSession Text
-  deriving (Generic, Show, ToEncoded, FromEncoded)
+  deriving (Generic, Show, ToJSON, FromJSON)
 instance Session InsecureSession where
   cookieSecure = False
 
@@ -32,7 +32,7 @@
   describe "Session" $ do
     it "should encode cookie" $ do
       let woot = Woot "hello"
-      toCookie woot `shouldBe` CookieValue (cs $ Encoded.encode woot)
+      toCookie woot `shouldBe` CookieValue (cs $ A.encode woot)
 
   describe "sessionCookie" $ do
     it "should create cookie" $ do
@@ -54,13 +54,13 @@
     it "should render complex cookie with included path" $ do
       let woot = Woot "hello world"
       let cookie = sessionCookie woot
-      Cookie.render [] cookie `shouldBe` "Woot=" <> urlEncode True (cs $ Encoded.encode woot) <> "; SameSite=None; secure; path=/somepage"
+      Cookie.render [] cookie `shouldBe` "Woot=" <> urlEncode True (cs $ A.encode woot) <> "; SameSite=None; secure; path=/somepage"
 
   describe "Session class" $ do
     it "should encode class" $ do
       let prefs = Preferences "hello" Warning
       let cooks = Cookie.insert (sessionCookie prefs) mempty
-      Cookie.lookup (sessionKey @Preferences) cooks `shouldBe` Just (CookieValue $ cs $ Encoded.encode prefs)
+      Cookie.lookup (sessionKey @Preferences) cooks `shouldBe` Just (CookieValue $ cs $ A.encode prefs)
 
     it "should decode class" $ do
       let prefs = Preferences "hello" Warning
@@ -78,7 +78,7 @@
   { message :: Text
   , color :: AppColor
   }
-  deriving (Generic, Eq, Show, ToEncoded, FromEncoded, Session)
+  deriving (Generic, Eq, Show, ToJSON, FromJSON, Session)
 instance Default Preferences where
   def = Preferences "_" White
 
@@ -97,4 +97,4 @@
   | PrimaryLight
   | Secondary
   | SecondaryLight
-  deriving (Show, Eq, Generic, ToParam, FromParam)
+  deriving (Show, Eq, Generic, ToJSON, FromJSON)
diff --git a/test/Test/ViewActionSpec.hs b/test/Test/ViewActionSpec.hs
--- a/test/Test/ViewActionSpec.hs
+++ b/test/Test/ViewActionSpec.hs
@@ -1,36 +1,52 @@
+{-# LANGUAGE BlockArguments #-}
+
 module Test.ViewActionSpec where
 
+import Data.Aeson (Value (..))
 import Data.Text (Text)
 import GHC.Generics
 import Skeletest
 import Skeletest.Predicate qualified as P
 import Web.Hyperbole (FromJSON, ToJSON)
+import Web.Hyperbole.Data.Argument
 import Web.Hyperbole.Data.Encoded
-import Web.Hyperbole.Data.Param
 import Web.Hyperbole.View
-import Web.Hyperbole.HyperView.Event (toActionInput)
 
 
 data Simple = Simple
-  deriving (Generic, Eq, Show, Read, ViewAction, ToJSON, FromJSON, ToParam, FromParam)
+  deriving (Generic, Eq, Show, ViewAction)
 
 
 data Product = Product String Int
-  deriving (Generic, Show, Eq, ViewAction, Read, ToJSON, FromJSON, ToEncoded, FromEncoded, ToParam, FromParam)
+  deriving (Generic, Show, Eq, ViewAction, Read, ToJSON, FromJSON, ToEncoded, FromEncoded)
 
 
 data Product' = Product' HasText Int
   deriving (Generic, Show, Eq, ViewAction, Read, ToJSON, FromJSON, ToEncoded, FromEncoded)
 
 
+data Something = Something
+  deriving (Generic, Show, ToJSON, FromJSON, Eq)
+
+
+-- If you want another type as an input, you must implement UserInput. We don't automatically parse integers from search fields
+newtype NumInput = NumInput Int
+  deriving (Generic, Show, Eq)
+  deriving anyclass (FromJSON, ToJSON)
+
+
 data Sum
   = SumA
   | SumB Int
   | SubC Text
   | SubD (Maybe Text)
   | SubE Term
-  | SubF Simple
-  deriving (Generic, Show, Read, Eq, ViewAction)
+  | SubF Something
+  | SubG Int Something
+  | SubH NumInput Int
+  | SubI Something Int
+  | SubJ Text NumInput
+  deriving (Generic, Show, Eq, ViewAction)
 
 
 data Compound = Compound Product
@@ -38,46 +54,41 @@
 
 
 data HasText = HasText Text
-  deriving (Generic, Show, Eq, Read, ViewAction, ToJSON, FromJSON, FromEncoded, ToEncoded, ToParam, FromParam)
+  deriving (Generic, Show, Eq, Read, ViewAction, ToJSON, FromJSON, FromEncoded, ToEncoded)
 
 
 newtype Term = Term Text
-  deriving newtype (Eq, Show, ToJSON, FromJSON, Read, ToParam, FromParam)
+  deriving newtype (Eq, Show, ToJSON, FromJSON, Read)
 
 
+data NumberPair = NumberPair {a :: Int, b :: NumInput}
+  deriving (Show, Generic, ToJSON, FromJSON)
+
+
+data Nested = Nested NumberPair
+  deriving (Generic, ViewAction)
+
+
 spec :: Spec
 spec = withMarkers ["encoded"] $ do
   describe "ViewAction" $ do
     describe "toAction" $ do
       it "simple" $ toAction Simple `shouldBe` Encoded "Simple" []
-      it "has text" $ toAction (HasText "hello world") `shouldBe` Encoded "HasText" ["hello world"]
-      it "product" $ toAction (Product "hello world" 123) `shouldBe` Encoded "Product" ["hello world", toParam @Int 123]
-      it "sum" $ toAction (SumB 123) `shouldBe` Encoded "SumB" [toParam @Int 123]
+      it "has text" $ toAction (HasText "hello world") `shouldBe` Encoded "HasText" [JSON $ String "hello world"]
+      it "product" $ toAction (Product "hello world" 123) `shouldBe` Encoded "Product" [JSON $ String "hello world", toArgument @Int 123]
+      it "sum" $ toAction (SumB 123) `shouldBe` Encoded "SumB" [toArgument @Int 123]
       it "compound" $ do
         let p = Product "hello world" 123
-        toAction (Compound p) `shouldBe` Encoded "Compound" [toParam p]
-
-    describe "toActionInput" $ do
-      it "Constructor Text" $ do
-        toActionInput SubC `shouldBe` Encoded "SubC" []
-
-      it "Constructor (Maybe Text)" $ do
-        toActionInput (SubD . Just) `shouldBe` Encoded "SubD" []
-
-      it "Constructor newtype Term" $ do
-        toActionInput (SubE . Term) `shouldBe` Encoded "SubE" []
-
-      it "renders data constructors" $ do
-        toActionInput SubF `shouldBe` Encoded "SubF" []
+        toAction (Compound p) `shouldBe` Encoded "Compound" [toArgument p]
 
     describe "parseAction" $ do
       it "simple" $ parseAction (Encoded "Simple" []) `shouldBe` pure Simple
 
       it "parse product" $ do
-        parseAction @Product (Encoded "Product" ["woot", toParam @Int 1234]) `shouldSatisfy` P.right P.anything
+        parseAction @Product (Encoded "Product" [JSON $ String "woot", toArgument @Int 1234]) `shouldSatisfy` P.right P.anything
 
       it "parse product with spaces" $ do
-        parseAction @Product (Encoded "Product" ["hello world", toParam @Int 1234]) `shouldSatisfy` P.right P.anything
+        parseAction @Product (Encoded "Product" [JSON $ String "hello world", toArgument @Int 1234]) `shouldSatisfy` P.right P.anything
 
     describe "roundTrip" $ do
       it "simple" $ do
diff --git a/test/Test/ViewIdSpec.hs b/test/Test/ViewIdSpec.hs
--- a/test/Test/ViewIdSpec.hs
+++ b/test/Test/ViewIdSpec.hs
@@ -11,7 +11,7 @@
 
 
 data Thing = Thing
-  deriving (Generic, Show, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded, ViewId, ToParam, FromParam)
+  deriving (Generic, Show, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded, ViewId)
 
 
 data Custom = Custom
@@ -27,7 +27,7 @@
   | Two Thing
   | WithId (Id Thing)
   | Compound Text Compound
-  deriving (Generic, Show, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded, ViewId, ToParam, FromParam)
+  deriving (Generic, Show, Eq, ToJSON, FromJSON, ToEncoded, FromEncoded, ViewId)
 
 
 data Product4 = Product4 Text Text Text Text
@@ -40,7 +40,7 @@
 
 
 newtype Id a = Id {fromId :: Text}
-  deriving newtype (Eq, ToJSON, FromJSON, Ord, Show, ToParam, FromParam)
+  deriving newtype (Eq, ToJSON, FromJSON, Ord, Show)
   deriving (Generic)
 
 
@@ -65,7 +65,7 @@
 
     describe "has-string" $ do
       it "should not contain single quotes" $ do
-        encodeViewId (HasString "woot") `shouldBe` "HasString woot"
+        encodeViewId (HasString "woot") `shouldBe` "HasString \"woot\""
         containsSingleQuotes (encodeViewId (HasString "woot")) `shouldBe` False
 
       it "should roundtrip" $ do
diff --git a/test/Test/ViewSpec.hs b/test/Test/ViewSpec.hs
--- a/test/Test/ViewSpec.hs
+++ b/test/Test/ViewSpec.hs
@@ -19,5 +19,5 @@
         renderText v `shouldBe` "<div>one</div>\n<div>two</div>"
 
       it "renders all nodes with >>=" $ do
-        let v = el "one" >>= \_ -> el "two"
+        let v = el "one" >> el "two"
         renderText v `shouldBe` "<div>one</div>\n<div>two</div>"
