go /

Coming back to color-transit

.md | permalink | Published on December 31, 2019

Note: I'm writing this as an experiment in coming back to a side-project that has long been sitting around, and quitetly.

Looking at it with old eyes and high level questions (of myself) like:

  • Does it still work?
  • Can I understand it?
  • Can I still run it locally?
  • Can I make changes to it?
  • How long does all of that take?

Background

Four-ish years ago, I wanted to try writing a project in ClojureScript.

A few years before that I wrote a bunch of Clojure and had just wanted a brief and fun refresher project with the langauge, but trying a different runtime.

I also wanted to play around with the canvas API, because I had never really done much drawing. Why limit yourself to one thing a time, right?

This project was color-transit. It's a single static page that cycles through color graidents, and boy does it do that.

You can see it live here. This link was actually surprising to me: it's running on this domain. In hindsight, of course it is, this is all github pages... I just didn't immediately realize that any repo that has this gh-pages branch would end up on this site. But here we are. Cool. Works as intended, I guess.

First look back at the repo

Wow, there's like no README at all.

I was definitely not doing this project for any kind of reproducability, just a playground.

It's using leiningen, which was the standard at the time when building clj/s projects, I'm not really sure if that's still the thing people use. Since then, or maybe even at that time, boot was a thing that started to exist. Both are mentioned in the clojure(script) docs.

The project uses lein-figwheel for live reloading, which is pretty neato.

Commits & Timeline

The first obvious thing I can see is that most of the work happened quickly, On the day before Christmas, and then the day after.

The commits on the days following were cleanup and reorganization, and somehow a few weeks later I came back to ship it to a gh-pages branch. I just checked to see why I decided to do this, maybe it was when the feature shipped, but looking at the docs, that wasn't it... I did this over two years after the last update to that documentation (Dec, 2013 vs Jan, 2016).

Also looking at the current way to ship to the branch, dang this is a mess compared to what I'm doing for this specific site. But it worked as well as it needed to.

Does it still work?

Yep. That's pretty cool- it's just some JS that's been hanging out on a CDN for the past few years.

Local development

Instead of installing everything (clojure and lein) locally, I am going to try and run all of this in a docker container.

First attempt

The Dockerfile....

FROM clojure:slim-buster
WORKDIR /usr/app

COPY project.clj .
RUN lein deps

And then running it...

docker build -t color-transit .
docker run --rm -v "$PWD":/usr/app color-transit lein figwheel

Looking through the checks notes, hundreds of lines of stack trace, I see Caused by: java.lang.ExceptionInInitializerError: null.

Full stack-trace
Warning: implicit hook found: leiningen.cljsbuild/activate
Hooks are deprecated and will be removed in a future version.
clojure.lang.Compiler$CompilerException: Syntax error compiling at (cljs/tagged_literals.cljc:1:1).
#:clojure.error{:phase :compile-syntax-check, :line 1, :column 1, :source "cljs/tagged_literals.cljc"}
 at clojure.lang.Compiler.load (Compiler.java:7647)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:805)
    cljs.analyzer$eval1658$loading__6706__auto____1659.invoke (analyzer.cljc:9)
    cljs.analyzer$eval1658.invokeStatic (analyzer.cljc:9)
    cljs.analyzer$eval1658.invoke (analyzer.cljc:9)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:457)
    figwheel_sidecar.utils$eval1652$loading__6706__auto____1653.invoke (utils.clj:1)
    figwheel_sidecar.utils$eval1652.invokeStatic (utils.clj:1)
    figwheel_sidecar.utils$eval1652.invoke (utils.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:551)
    figwheel_sidecar.config$eval1646$loading__6706__auto____1647.invoke (config.clj:1)
    figwheel_sidecar.config$eval1646.invokeStatic (config.clj:1)
    figwheel_sidecar.config$eval1646.invoke (config.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:457)
    leiningen.figwheel$eval1640$loading__6706__auto____1641.invoke (figwheel.clj:1)
    leiningen.figwheel$eval1640.invokeStatic (figwheel.clj:1)
    leiningen.figwheel$eval1640.invoke (figwheel.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    leiningen.core.utils$require_resolve.invokeStatic (utils.clj:102)
    leiningen.core.utils$require_resolve.invoke (utils.clj:95)
    leiningen.core.utils$require_resolve.invokeStatic (utils.clj:105)
    leiningen.core.utils$require_resolve.invoke (utils.clj:95)
    leiningen.core.main$lookup_task_var.invokeStatic (main.clj:69)
    leiningen.core.main$lookup_task_var.invoke (main.clj:65)
    leiningen.core.main$pass_through_help_QMARK_.invokeStatic (main.clj:79)
    leiningen.core.main$pass_through_help_QMARK_.invoke (main.clj:73)
    leiningen.core.main$task_args.invokeStatic (main.clj:82)
    leiningen.core.main$task_args.invoke (main.clj:81)
    leiningen.core.main$resolve_and_apply.invokeStatic (main.clj:339)
    leiningen.core.main$resolve_and_apply.invoke (main.clj:336)
    leiningen.core.main$_main$fn__6681.invoke (main.clj:452)
    leiningen.core.main$_main.invokeStatic (main.clj:442)
    leiningen.core.main$_main.doInvoke (main.clj:439)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.core$apply.invokeStatic (core.clj:665)
    clojure.main$main_opt.invokeStatic (main.clj:491)
    clojure.main$main_opt.invoke (main.clj:487)
    clojure.main$main.invokeStatic (main.clj:598)
    clojure.main$main.doInvoke (main.clj:561)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.main.main (main.java:37)
Caused by: java.lang.ExceptionInInitializerError: null
 at java.lang.Class.forName0 (Class.java:-2)
    java.lang.Class.forName (Class.java:398)
    clojure.lang.RT.classForName (RT.java:2207)
    clojure.lang.RT.classForName (RT.java:2216)
    clojure.lang.RT.loadClassForName (RT.java:2235)
    clojure.lang.RT.load (RT.java:453)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    cljs.tagged_literals$eval2011$loading__6706__auto____2012.invoke (tagged_literals.cljc:1)
    cljs.tagged_literals$eval2011.invokeStatic (tagged_literals.cljc:1)
    cljs.tagged_literals$eval2011.invoke (tagged_literals.cljc:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:805)
    cljs.analyzer$eval1658$loading__6706__auto____1659.invoke (analyzer.cljc:9)
    cljs.analyzer$eval1658.invokeStatic (analyzer.cljc:9)
    cljs.analyzer$eval1658.invoke (analyzer.cljc:9)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:457)
    figwheel_sidecar.utils$eval1652$loading__6706__auto____1653.invoke (utils.clj:1)
    figwheel_sidecar.utils$eval1652.invokeStatic (utils.clj:1)
    figwheel_sidecar.utils$eval1652.invoke (utils.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:551)
    figwheel_sidecar.config$eval1646$loading__6706__auto____1647.invoke (config.clj:1)
    figwheel_sidecar.config$eval1646.invokeStatic (config.clj:1)
    figwheel_sidecar.config$eval1646.invoke (config.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:457)
    leiningen.figwheel$eval1640$loading__6706__auto____1641.invoke (figwheel.clj:1)
    leiningen.figwheel$eval1640.invokeStatic (figwheel.clj:1)
    leiningen.figwheel$eval1640.invoke (figwheel.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    leiningen.core.utils$require_resolve.invokeStatic (utils.clj:102)
    leiningen.core.utils$require_resolve.invoke (utils.clj:95)
    leiningen.core.utils$require_resolve.invokeStatic (utils.clj:105)
    leiningen.core.utils$require_resolve.invoke (utils.clj:95)
    leiningen.core.main$lookup_task_var.invokeStatic (main.clj:69)
    leiningen.core.main$lookup_task_var.invoke (main.clj:65)
    leiningen.core.main$pass_through_help_QMARK_.invokeStatic (main.clj:79)
    leiningen.core.main$pass_through_help_QMARK_.invoke (main.clj:73)
    leiningen.core.main$task_args.invokeStatic (main.clj:82)
    leiningen.core.main$task_args.invoke (main.clj:81)
    leiningen.core.main$resolve_and_apply.invokeStatic (main.clj:339)
    leiningen.core.main$resolve_and_apply.invoke (main.clj:336)
    leiningen.core.main$_main$fn__6681.invoke (main.clj:452)
    leiningen.core.main$_main.invokeStatic (main.clj:442)
    leiningen.core.main$_main.doInvoke (main.clj:439)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.core$apply.invokeStatic (core.clj:665)
    clojure.main$main_opt.invokeStatic (main.clj:491)
    clojure.main$main_opt.invoke (main.clj:487)
    clojure.main$main.invokeStatic (main.clj:598)
    clojure.main$main.doInvoke (main.clj:561)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.main.main (main.java:37)
Caused by: java.lang.ClassNotFoundException: java/sql/Timestamp
 at java.lang.Class.forName0 (Class.java:-2)
    java.lang.Class.forName (Class.java:398)
    clojure.lang.RT.classForName (RT.java:2207)
    clojure.lang.RT.classForNameNonLoading (RT.java:2220)
    clojure.instant/loading__6706__auto__ (instant.clj:9)
    clojure.instant__init.load (:9)
    clojure.instant__init.<clinit> (:-1)
    java.lang.Class.forName0 (Class.java:-2)
    java.lang.Class.forName (Class.java:398)
    clojure.lang.RT.classForName (RT.java:2207)
    clojure.lang.RT.classForName (RT.java:2216)
    clojure.lang.RT.loadClassForName (RT.java:2235)
    clojure.lang.RT.load (RT.java:453)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    cljs.tagged_literals$eval2011$loading__6706__auto____2012.invoke (tagged_literals.cljc:1)
    cljs.tagged_literals$eval2011.invokeStatic (tagged_literals.cljc:1)
    cljs.tagged_literals$eval2011.invoke (tagged_literals.cljc:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:805)
    cljs.analyzer$eval1658$loading__6706__auto____1659.invoke (analyzer.cljc:9)
    cljs.analyzer$eval1658.invokeStatic (analyzer.cljc:9)
    cljs.analyzer$eval1658.invoke (analyzer.cljc:9)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:457)
    figwheel_sidecar.utils$eval1652$loading__6706__auto____1653.invoke (utils.clj:1)
    figwheel_sidecar.utils$eval1652.invokeStatic (utils.clj:1)
    figwheel_sidecar.utils$eval1652.invoke (utils.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:551)
    figwheel_sidecar.config$eval1646$loading__6706__auto____1647.invoke (config.clj:1)
    figwheel_sidecar.config$eval1646.invokeStatic (config.clj:1)
    figwheel_sidecar.config$eval1646.invoke (config.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:457)
    leiningen.figwheel$eval1640$loading__6706__auto____1641.invoke (figwheel.clj:1)
    leiningen.figwheel$eval1640.invokeStatic (figwheel.clj:1)
    leiningen.figwheel$eval1640.invoke (figwheel.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7165)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.RT.loadResourceScript (RT.java:381)
    clojure.lang.RT.loadResourceScript (RT.java:372)
    clojure.lang.RT.load (RT.java:463)
    clojure.lang.RT.load (RT.java:428)
    clojure.core$load$fn__6824.invoke (core.clj:6126)
    clojure.core$load.invokeStatic (core.clj:6125)
    clojure.core$load.doInvoke (core.clj:6109)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$load_one.invokeStatic (core.clj:5908)
    clojure.core$load_one.invoke (core.clj:5903)
    clojure.core$load_lib$fn__6765.invoke (core.clj:5948)
    clojure.core$load_lib.invokeStatic (core.clj:5947)
    clojure.core$load_lib.doInvoke (core.clj:5928)
    clojure.lang.RestFn.applyTo (RestFn.java:142)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$load_libs.invokeStatic (core.clj:5985)
    clojure.core$load_libs.doInvoke (core.clj:5969)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$require.invokeStatic (core.clj:6007)
    clojure.core$require.doInvoke (core.clj:6007)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    leiningen.core.utils$require_resolve.invokeStatic (utils.clj:102)
    leiningen.core.utils$require_resolve.invoke (utils.clj:95)
    leiningen.core.utils$require_resolve.invokeStatic (utils.clj:105)
    leiningen.core.utils$require_resolve.invoke (utils.clj:95)
    leiningen.core.main$lookup_task_var.invokeStatic (main.clj:69)
    leiningen.core.main$lookup_task_var.invoke (main.clj:65)
    leiningen.core.main$pass_through_help_QMARK_.invokeStatic (main.clj:79)
    leiningen.core.main$pass_through_help_QMARK_.invoke (main.clj:73)
    leiningen.core.main$task_args.invokeStatic (main.clj:82)
    leiningen.core.main$task_args.invoke (main.clj:81)
    leiningen.core.main$resolve_and_apply.invokeStatic (main.clj:339)
    leiningen.core.main$resolve_and_apply.invoke (main.clj:336)
    leiningen.core.main$_main$fn__6681.invoke (main.clj:452)
    leiningen.core.main$_main.invokeStatic (main.clj:442)
    leiningen.core.main$_main.doInvoke (main.clj:439)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.core$apply.invokeStatic (core.clj:665)
    clojure.main$main_opt.invokeStatic (main.clj:491)
    clojure.main$main_opt.invoke (main.clj:487)
    clojure.main$main.invokeStatic (main.clj:598)
    clojure.main$main.doInvoke (main.clj:561)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.main.main (main.java:37)

Down to JDK8

Changing the base image to clojure:openjdk-8-slim-buster fixes the initial null pointer exception, but now I'm getting another one.

Second stack-trace
Warning: implicit hook found: leiningen.cljsbuild/activate
Hooks are deprecated and will be removed in a future version.
Exception in thread "main" java.io.FileNotFoundException: Could not locate clojure/tools/nrepl/middleware/interruptible_eval__init.class or clojure/tools/nrepl/middleware/interruptible_eval.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name., compiling:(figwheel_sidecar/repl.clj:1:1)
	at clojure.lang.Compiler.load(Compiler.java:7239)
	at clojure.lang.RT.loadResourceScript(RT.java:371)
	at clojure.lang.RT.loadResourceScript(RT.java:362)
	at clojure.lang.RT.load(RT.java:446)
	at clojure.lang.RT.load(RT.java:412)
	at clojure.core$load$fn__5448.invoke(core.clj:5866)
	at clojure.core$load.doInvoke(core.clj:5865)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invoke(core.clj:5671)
	at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
	at clojure.core$load_lib.doInvoke(core.clj:5710)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$load_libs.doInvoke(core.clj:5749)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$require.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.invoke(RestFn.java:1289)
	at figwheel_sidecar.system$eval15$loading__5340__auto____16.invoke(system.clj:1)
	at figwheel_sidecar.system$eval15.invoke(system.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:6782)
	at clojure.lang.Compiler.eval(Compiler.java:6771)
	at clojure.lang.Compiler.load(Compiler.java:7227)
	at clojure.lang.RT.loadResourceScript(RT.java:371)
	at clojure.lang.RT.loadResourceScript(RT.java:362)
	at clojure.lang.RT.load(RT.java:446)
	at clojure.lang.RT.load(RT.java:412)
	at clojure.core$load$fn__5448.invoke(core.clj:5866)
	at clojure.core$load.doInvoke(core.clj:5865)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invoke(core.clj:5671)
	at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
	at clojure.core$load_lib.doInvoke(core.clj:5710)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$load_libs.doInvoke(core.clj:5749)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$require.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.invoke(RestFn.java:457)
	at figwheel_sidecar.repl_api$eval9$loading__5340__auto____10.invoke(repl_api.clj:1)
	at figwheel_sidecar.repl_api$eval9.invoke(repl_api.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:6782)
	at clojure.lang.Compiler.eval(Compiler.java:6771)
	at clojure.lang.Compiler.load(Compiler.java:7227)
	at clojure.lang.RT.loadResourceScript(RT.java:371)
	at clojure.lang.RT.loadResourceScript(RT.java:362)
	at clojure.lang.RT.load(RT.java:446)
	at clojure.lang.RT.load(RT.java:412)
	at clojure.core$load$fn__5448.invoke(core.clj:5866)
	at clojure.core$load.doInvoke(core.clj:5865)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invoke(core.clj:5671)
	at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
	at clojure.core$load_lib.doInvoke(core.clj:5710)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$load_libs.doInvoke(core.clj:5749)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$require.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at user$eval5.invoke(form-init37068904552664761.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:6782)
	at clojure.lang.Compiler.eval(Compiler.java:6771)
	at clojure.lang.Compiler.load(Compiler.java:7227)
	at clojure.lang.Compiler.loadFile(Compiler.java:7165)
	at clojure.main$load_script.invoke(main.clj:275)
	at clojure.main$init_opt.invoke(main.clj:280)
	at clojure.main$initialize.invoke(main.clj:308)
	at clojure.main$null_opt.invoke(main.clj:343)
	at clojure.main$main.doInvoke(main.clj:421)
	at clojure.lang.RestFn.invoke(RestFn.java:421)
	at clojure.lang.Var.invoke(Var.java:383)
	at clojure.lang.AFn.applyToHelper(AFn.java:156)
	at clojure.lang.Var.applyTo(Var.java:700)
	at clojure.main.main(main.java:37)
Caused by: java.io.FileNotFoundException: Could not locate clojure/tools/nrepl/middleware/interruptible_eval__init.class or clojure/tools/nrepl/middleware/interruptible_eval.clj on classpath. Please check that namespaces with dashes use underscores in the Clojure file name.
	at clojure.lang.RT.load(RT.java:449)
	at clojure.lang.RT.load(RT.java:412)
	at clojure.core$load$fn__5448.invoke(core.clj:5866)
	at clojure.core$load.doInvoke(core.clj:5865)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invoke(core.clj:5671)
	at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
	at clojure.core$load_lib.doInvoke(core.clj:5710)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$load_libs.doInvoke(core.clj:5749)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invoke(core.clj:632)
	at clojure.core$require.doInvoke(core.clj:5832)
	at clojure.lang.RestFn.invoke(RestFn.java:703)
	at figwheel_sidecar.repl$eval2185$loading__5340__auto____2186.invoke(repl.clj:1)
	at figwheel_sidecar.repl$eval2185.invoke(repl.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:6782)
	at clojure.lang.Compiler.eval(Compiler.java:6771)
	at clojure.lang.Compiler.load(Compiler.java:7227)
	... 76 more
Subprocess failed

It's not all bad though, if I run lien cljsbuild once in the container, I get some good news...

Compiling "resources/public/js/main.js" from ["src"]...
Successfully compiled "resources/public/js/main.js" in 2.4 seconds.

Nice.

Version mismatches & figwheel

My assumption here is that I needed to update/specify all of the correct versions for Clojure, ClojureScript, and Figwheel. I looked up a working example from this flappy bird demo.

diff --git a/project.clj b/project.clj
index 644deab..babc3f0 100644
--- a/project.clj
+++ b/project.clj
@@ -1,9 +1,8 @@
 (defproject color-transit "0.1.0-SNAPSHOT"
-  :dependencies [[org.clojure/clojure "1.7.0"]
-                 [org.clojure/clojurescript "1.7.170"]]
-  :plugins [[lein-figwheel "0.5.0-1"]
-            [lein-cljsbuild "1.1.2"]]
-  :hooks [leiningen.cljsbuild]
+  :dependencies [[org.clojure/clojure "1.9.0"]
+                 [org.clojure/clojurescript "1.10.312"]]
+  :plugins [[lein-cljsbuild "1.1.4" :exclusions [org.clojure/clojure]]
+            [lein-figwheel "0.5.16"]]
   :clean-targets ^{:protect false} [:target-path "out" "resources/public/js"]
   :cljsbuild {
     :builds {

This ended up telling me that the app was running on port 3449, so I needed to udpate the command...

docker run -it --rm -v "$PWD":/usr/app -p 3449:3449 color-transit lein figwheel

Though this works, the issue is that it downloads all of the figwheel dependencies each time it runs (for the first time).

The trick is to add this to the Dockerfile.

RUN lein figwheel :check-config

I found this looking around the source code looking for "Figwheel: Cutting some fruit, just a sec..." and finding that I could run :check-config to download all of the dependencies.

Aside: Apparently there's an entirely new version called figwheel-main, but I'm not going to go all the way over there just yet.

How long did all that take?

To answer one of the questions I asked myself in the beginning... getting a dev environment up and running for this took about two hours.

Getting a blog post out and running took another month.

The gh-pages script

Looking at this, it's obviously a bunch of stuff that I had copy pasted from other places on the internet... all of the whitespace is a mixture of tabs and spaces- and it just kind of works well enough.

ClojureScript?

src/
└── color-transit
    ├── canvas.cljs
    ├── canvas_set.cljs
    ├── color.cljs
    ├── core.cljs
    ├── dims.cljs
    └── interval.cljs

Most of the effor here was going back to get this running and operational, and I also wanted to jump into the cljs a bit to see if I can traverse it at all, or if I could make some kind of substantial change and deploy it. But I don't really want to be doing that. Let's dig into the meat of the "app."

JS Wrappers

interval, canvas, dims... These are all wrappers around "native" JS methods in order to have them appear to be, or operate as immutable values, like clojure expects idiomatically.

For example:

(defn fill-rect
  [ctx x0 y0 x1 y1]
  (.fillRect ctx x0 y0 x1 y1)
  ctx)

(defn ctx
  "Apply f to the context of the canvas, return the canvas.
   This is useful for chaining ctx methods.
   (-> canvas
       (ctx fill-style ...)
       (ctx fill-rect ...))"
  [canvas f & args]
  (apply f (:ctx canvas) args)
  canvas)

These make it so that it's easier to chain methods, and apply transformations to the canvas context in a real clojure-y way.

Functions that do mutation of global state, (like the window), have ! at the end. For example full-screen! or start-app!.

The fun part

or, how this entire thing actually works.

The app's html is defined in public/index.html, and when the it starts, we create the canvas context using query->Canvas, this is a container that holds the 2d context of the canvas element in the DOM, as well as the actual element, and dimensions, defined as:

(defrecord Canvas [el ctx w h])

The above js wrapping functions, operate on Canvas records.

When the app starts it creates a CanvasSet, which links the Canvas to the color-sets that it will be running through. The application state operates pretty much solely on a CanvasSet.

Startup

The app starts with a few pre-defined colors:

(let [colors [[0 10 0]
              [200 155 255]
              [40 40 40]
              [255 0 0]
              [0 255 255]
              [100 233 67]]]
  ;; ...
)

We take these colors (all [r g b] format), and randomize their order into num-sets. This is stored in the app-state, and we can live inspect it using the lein repl that get started with figwheel app.

dev:cljs.user=> (in-ns 'color-transit.core)
dev:color-transit.core=> (->> @app-state :canvas-sets first :color-sets (map :colors))
([[0 10 0] [100 233 67] [255 0 0] [0 255 255] [40 40 40] [200 155 255]]
 [[200 155 255] [0 10 0] [40 40 40] [255 0 0] [100 233 67] [0 255 255]]
 [[40 40 40] [0 255 255] [0 10 0] [200 155 255] [255 0 0] [100 233 67]])

In our start up we say we want 3 sets, and each one has a shuffle-d list of the original colors provided. The three sets correspond to three stops in the generated gradient, and the colors that they will be transitioning to.

An initial color-set looks like:

{ :colors colors
  :color-queue [ ]
  :current-color: (first colors) }
  • :colors - are each of the colors in that one gradient step we'll be fading through,
  • :color-queue - a queue (FIFO), of colors that we'll be moving through in the fade,
  • :current-color - what color are we currently displaying.

Our canvas drawer (in core) will only draw the current-color, and we pre-compute the queue up front as infrequently as possible so that we do as little work when drawing the gradient as possible.

All of that happens in color/compute-next-state.

Computing the color transitios

compute-next-state has two main branches of logic:

  1. the first is when the queue is non-empty, we take the color at the head of the queue, and update current-color with it. This runs until the queue is drained.

  2. the second is if both the initial state, and happens after the queue is drained... we generate a new one! Given the current color, and the next one in the colors, and the number of steps we want to be able to take from one color to the next, we compute them.

The maths
(defn delta [n1 n2 steps] (/ (- n2 n1) steps))

delta is simplified version of what the app does, where this operates on one number, the app works on the destructured [r g b].

(defn +delta [n n+ scalar]
  (-> (* scalar n+)
      (+ n)
      Math/floor))

Similarly, +delta increases n by the delta we computed earlier, n+, scaled up by scalar, and then rounds it down.

We use these two functions to create the color queue.

Most of everything else in the code is transforming the colors into the gradient and canvas, and making sure it's running at a specific interval in the browser.

Wrapping up

Originally, I wasn't sure if I'd want to change anything here, but after spending the time with the codebase, I'd like to make the grandient drawing reactive to user parameterization in the UI.

So eventually, there'll be a second post, and updates to the app, and a write up of the process.

...which will hopefully take less than a couple of months to finish...

Update

As of 2023, I have not come back to this, and I don't think I will.