diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Shpadoinkle Debug aka S11 Debug
+Copyright © 2020 Isaac Shapira
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of Shpadoinkle nor the names of its contributors may be
+   used to endorse or promote products derived from this software without
+   specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+# Shpadoinkle Console
+
+[![Goldwater](https://gitlab.com/fresheyeball/Shpadoinkle/badges/master/pipeline.svg)](https://gitlab.com/fresheyeball/Shpadoinkle)
+[![Haddock](https://img.shields.io/badge/haddock-master-informational)](https://shpadoinkle.org/console)
+[![BSD-3](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
+[![built with nix](https://img.shields.io/badge/built%20with-nix-41439a)](https://builtwithnix.org)
+[![Hackage](https://img.shields.io/hackage/v/Shpadoinkle-console.svg)](https://hackage.haskell.org/package/Shpadoinkle-console)
+[![Hackage Deps](https://img.shields.io/hackage-deps/v/Shpadoinkle-console.svg)](http://packdeps.haskellers.com/reverse/Shpadoinkle-console)
+[![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/Shpadoinkle-console/badge)](https://matrix.hackage.haskell.org/#/package/Shpadoinkle-console)
+
+This package exposes some useful debugging functions for tracing the state of Shpadoinkle applications as they change over time. It exposes some type classes currently.
+
+```haskell
+class LogJS (c :: Type -> Constraint) where
+  logJS :: c a => a -> JSM ()
+
+class LogJS c => Trapper c where
+  trapper :: c a => JSContextRef -> a -> a
+
+class Assert (c :: Type -> Constraint) where
+  assert :: c a => Bool -> a -> JSM ()
+```
+
+These are intended to by used with `TypeApplications`, so you can choose the means of conversion before passing to `console.log`. For example:
+
+```haskell
+main :: IO ()
+main = runJSorWarp 8080 $ do
+  ctx <- askJSM
+  simple runParDiff initial (view . trapper @ToJSON ctx) getBody
+```
+
+This will log all state by first encoding to JSON with Aeson, then then logging with `JSON.parse` so the browser console has the nice native display. If we change it to `trapper @Show ctx` it will use the `Show` instance instead.
+
+We also export a handful of `console` bindings such as `console.time`, `console.table`, `console.info`, `console.warn`, `console.debug`, and of course `console.log`.
diff --git a/Shpadoinkle-console.cabal b/Shpadoinkle-console.cabal
new file mode 100644
--- /dev/null
+++ b/Shpadoinkle-console.cabal
@@ -0,0 +1,42 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.32.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: ed7c83dc76bc4f63bd8d401f7b00f44eae07c11d062de89532fc81d93598aa13
+
+name:           Shpadoinkle-console
+version:        0.0.1.0
+synopsis:       Support for the native browser console
+description:    This package adds support for native browser console features for logging and debugging.
+category:       Web
+author:         Isaac Shapira
+maintainer:     fresheyeball@protonmail.com
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://gitlab.com/fresheyeball/Shpadoinkle.git
+
+library
+  exposed-modules:
+      Shpadoinkle.Console
+  other-modules:
+      Paths_Shpadoinkle_console
+  hs-source-dirs:
+      ./.
+  ghc-options: -Wall -Wcompat -fwarn-redundant-constraints -fwarn-incomplete-uni-patterns -fwarn-tabs -fwarn-incomplete-record-updates -fwarn-identities
+  build-depends:
+      aeson >=1.4.4 && <1.5
+    , base >=4.12.0 && <4.16
+    , jsaddle >=0.9.7 && <0.20
+    , lens >=4.17.1 && <5.0
+    , text >=1.2.3 && <1.3
+    , unliftio >=0.2.12 && <0.3
+  default-language: Haskell2010
diff --git a/Shpadoinkle/Console.hs b/Shpadoinkle/Console.hs
new file mode 100644
--- /dev/null
+++ b/Shpadoinkle/Console.hs
@@ -0,0 +1,183 @@
+{-# LANGUAGE AllowAmbiguousTypes        #-}
+{-# LANGUAGE ConstraintKinds            #-}
+{-# LANGUAGE ExtendedDefaultRules       #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE KindSignatures             #-}
+{-# LANGUAGE OverloadedStrings          #-}
+{-# LANGUAGE ScopedTypeVariables        #-}
+{-# LANGUAGE TypeApplications           #-}
+{-# OPTIONS_GHC -fno-warn-type-defaults #-}
+
+
+{-|
+  This module exposes the browser's native console logging and debugging features,
+  including underutilized features such as time measurement, table displays, and assertions.
+-}
+
+
+module Shpadoinkle.Console (
+  -- * Classes
+  LogJS (..), Assert (..), Trapper (..), askJSM
+  -- * Native methods
+  -- ** Log levels
+  , log, debug, info, warn
+  -- ** Fancy display
+  , table
+  -- ** Time Measurement
+  , TimeLabel(..), time, timeEnd
+  ) where
+
+
+import           Control.Lens
+import           Data.Aeson                  (ToJSON, encode)
+import           Data.Kind
+import           Data.String
+import           Data.Text
+import           Data.Text.Lazy              (toStrict)
+import           Data.Text.Lazy.Encoding
+import           Language.Javascript.JSaddle hiding (startTime)
+import           Prelude                     hiding (log)
+import           System.IO.Unsafe            (unsafePerformIO)
+
+
+default (Text)
+
+{-|
+   'LogJS' is the base class for logging to the browser console.
+   Browser consoles contain rich tooling for exploring JavaScript objects,
+   DOM nodes, and much more. To take advantage of these native features, we
+   need to choose how we are going to log. The 'LogJS' class is intended to
+   be used in conjunction with 'TypeApplications'.
+
+   @
+   data Person = Person { first :: String, last :: String, age :: Int } deriving (Generic, ToJSON)
+   main = logJS @ToJSON "log" $ Person "bob" "saget" 45
+   @
+
+   is effectively equivalent to:
+
+   @
+   console.log({first: "bob", last: "saget", age: 45})
+   @
+
+   in that the console will render with nice expand/collapse object exploration features.
+-}
+class LogJS (c :: Type -> Constraint) where
+  logJS :: c a => Text -> a -> JSM ()
+
+
+-- | Logs against 'ToJSON' will be encoded via 'Aeson' then parsed using
+-- native <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse JSON.parse> before being sent to the console.
+instance LogJS ToJSON where
+  logJS t a = do
+    console <- jsg "console"
+    json    <- jsg "JSON"
+    parsed  <- json ^. js1 "parse" (toStrict . decodeUtf8 $ encode a)
+    () <$ console ^. js1 t parsed
+
+
+-- | Logs against 'Show' will be converted to a 'String' before being sent to the console.
+instance LogJS Show where
+  logJS t a = do
+    console <- jsg "console"
+    () <$ console ^. js1 t (pack $ show a)
+
+
+-- | Logs against 'ToJSVal' will be converted to a 'JSVal' before being sent to the console.
+instance LogJS ToJSVal where
+  logJS t a = do
+    console <- jsg "console"
+    () <$ console ^. js1 t (toJSVal a)
+
+
+{-|
+  Trapper is a class intended for continuous logging of your application and the catching of helpless animals.
+  Usage is along the lines of 'Debug.Trace.trace' where the effect of logging is implicit.
+  To make this work in both GHC and GHCjs contexts, you do need to
+  pass the 'JSContextRef' in manually ('askJSM' re-exported here for convenience).
+
+  @
+  main :: IO ()
+  main = runJSorWarp 8080 $ do
+    ctx <- askJSM
+    simple runParDiff initial (view . trapper @ToJSON ctx) getBody
+  @
+-}
+class LogJS c => Trapper c where
+  trapper :: c a => JSContextRef -> a -> a
+  trapper ctx x = unsafePerformIO $ runJSM (x <$ debug @c x) ctx
+  {-# NOINLINE trapper #-}
+
+instance Trapper ToJSON
+instance Trapper Show
+instance Trapper ToJSVal
+
+
+{-|
+  Assert is a class for assertion programming. It behaves the same as 'LogJS' but calls
+  <https://developer.mozilla.org/en-US/docs/Web/API/Console/assert console.assert> instead of
+  other console methods. This will only have an effect if the 'Bool' provided to 'assert' is 'False'.
+-}
+class Assert (c :: Type -> Constraint) where
+  assert :: c a => Bool -> a -> JSM ()
+
+instance Assert ToJSON where
+  assert b x = do
+    console <- jsg "console"
+    json <- jsg "JSON"
+    parsed <- json ^. js1 "parse" (toStrict . decodeUtf8 $ encode x)
+    () <$ console ^. js2 "assert" (toJSVal b) parsed
+
+instance Assert Show where
+  assert b x = do
+    console <- jsg "console"
+    () <$ console ^. js2 "assert" (toJSVal b) (pack $ show x)
+
+instance Assert ToJSVal where
+  assert b x = do
+    console <- jsg "console"
+    () <$ console ^. js2 "assert" (toJSVal b) (toJSVal x)
+
+
+-- | Log a list of JSON objects to the console where it will rendered as a table using <https://developer.mozilla.org/en-US/docs/Web/API/Console/table console.table>
+table :: ToJSON a => [a] -> JSM ()
+table = logJS @ToJSON "table"
+
+
+-- | Log to the console using <https://developer.mozilla.org/en-US/docs/Web/API/Console/log console.log>
+log :: forall c a. LogJS c => c a => a -> JSM ()
+log = logJS @c "log"
+
+
+-- | Log with the "warn" log level using <https://developer.mozilla.org/en-US/docs/Web/API/Console/warn console.warn>
+warn :: forall c a. LogJS c => c a => a -> JSM ()
+warn = logJS @c "warn"
+
+
+-- | Log with the "info" log level using <https://developer.mozilla.org/en-US/docs/Web/API/Console/info console.info>
+info :: forall c a. LogJS c => c a => a -> JSM ()
+info = logJS @c "info"
+
+
+-- | Log with the "debug" log level using <https://developer.mozilla.org/en-US/docs/Web/API/Console/debug console.debug>
+debug :: forall c a. LogJS c => c a => a -> JSM ()
+debug = logJS @c "debug"
+
+
+-- | A unique label for a timer. This is used to tie calls to <https://developer.mozilla.org/en-US/docs/Web/API/Console/time console.time> to <https://developer.mozilla.org/en-US/docs/Web/API/Console/timeEnd console.timeEnd>
+newtype TimeLabel = TimeLabel { unTimeLabel :: Text }
+  deriving (Eq, Ord, Show, IsString)
+
+
+-- | Start a timer using <https://developer.mozilla.org/en-US/docs/Web/API/Console/time console.time>
+time :: TimeLabel -> JSM ()
+time (TimeLabel l) = do
+  console <- jsg "console"
+  () <$ console ^. js1 "time" l
+
+
+-- | End a timer and print the milliseconds elapsed since it started using <https://developer.mozilla.org/en-US/docs/Web/API/Console/timeEnd console.timeEnd>
+timeEnd :: TimeLabel -> JSM ()
+timeEnd (TimeLabel l) = do
+  console <- jsg "console"
+  () <$ console ^. js1 "timeEnd" l
