diff --git a/res/decoder-ring/datatypes.js b/res/decoder-ring/datatypes.js
new file mode 100644
index 0000000000..93a779e079
--- /dev/null
+++ b/res/decoder-ring/datatypes.js
@@ -0,0 +1,107 @@
+/*
+ * Quick-n-dirty algebraic datatypes.
+ *
+ * These let us handle the possibility of failure without having to constantly write code to check for it.
+ * We can apply all of the transformations we need as if the data is present using `map`.
+ * If there's a None, or a FetchError, or a Pending, those are left untouched.
+ *
+ * I've used perhaps an odd bit of terminology from scalaz in `fold`. This is basically a `switch` statement:
+ * You pass it a set of functions to handle the various different states of the datatype, and if it finds the
+ * function it'll call it on its value.
+ *
+ * It's handy to have this in functional style when dealing with React as we can dispatch different ways of rendering
+ * really simply:
+ * ```
+ * bundleFetchStatus.fold({
+ * some: (fetchStatus) => ,
+ * }),
+ * ```
+ */
+
+
+class Optional {
+ static from(value) {
+ return value && Some.of(value) || None;
+ }
+ map(f) {
+ return this;
+ }
+ flatMap(f) {
+ return this;
+ }
+ fold({ none }) {
+ return none && none();
+ }
+}
+class Some extends Optional {
+ constructor(value) {
+ super();
+ this.value = value;
+ }
+ map(f) {
+ return Some.of(f(this.value));
+ }
+ flatMap(f) {
+ return f(this.value);
+ }
+ fold({ some }) {
+ return some && some(this.value);
+ }
+ static of(value) {
+ return new Some(value);
+ }
+}
+const None = new Optional();
+
+class FetchStatus {
+ constructor(opt = {}) {
+ this.opt = { at: Date.now(), ...opt };
+ }
+ map(f) {
+ return this;
+ }
+ flatMap(f) {
+ return this;
+ }
+}
+class Success extends FetchStatus {
+ static of(value) {
+ return new Success(value);
+ }
+ constructor(value, opt) {
+ super(opt);
+ this.value = value;
+ }
+ map(f) {
+ return new Success(f(this.value), this.opt);
+ }
+ flatMap(f) {
+ return f(this.value, this.opt);
+ }
+ fold({ success }) {
+ return success instanceof Function ? success(this.value, this.opt) : undefined;
+ }
+}
+class Pending extends FetchStatus {
+ static of(opt) {
+ return new Pending(opt);
+ }
+ constructor(opt) {
+ super(opt);
+ }
+ fold({ pending }) {
+ return pending instanceof Function ? pending(this.opt) : undefined;
+ }
+}
+class FetchError extends FetchStatus {
+ static of(reason, opt) {
+ return new FetchError(reason, opt);
+ }
+ constructor(reason, opt) {
+ super(opt);
+ this.reason = reason;
+ }
+ fold({ error }) {
+ return error instanceof Function ? error(this.reason, this.opt) : undefined;
+ }
+}
diff --git a/res/decoder-ring/decoder.js b/res/decoder-ring/decoder.js
new file mode 100644
index 0000000000..68c6bb066b
--- /dev/null
+++ b/res/decoder-ring/decoder.js
@@ -0,0 +1,319 @@
+class StartupError extends Error {}
+
+/*
+ * We need to know the bundle path before we can fetch the sourcemap files. In a production environment, we can guess
+ * it using this.
+ */
+async function getBundleName() {
+ const res = await fetch("../index.html");
+ if (!res.ok) {
+ throw new StartupError(`Couldn't fetch index.html to prefill bundle; ${res.status} ${res.statusText}`);
+ }
+ const index = await res.text();
+ return index.split("\n").map((line) =>
+ line.match(/
+
+
+
+
+
+
+
+
+
+
+
+
+ Waiting for javascript to run...
+
+
+