Building 40Hz Cognitive Focus Apps in Coni WASM

WebAssembly in the browser has always promised incredible performance, but bridging it smoothly with Web APIs like WebAudio and WebGL can sometimes feel like solving a puzzle. Today, we’re thrilled to showcase how the Coni ecosystem elegantly solves this by introducing two brand-new web apps designed for cognitive focus.

These two applications generate a 40Hz gamma frequency—a specific brain wave frequency associated with deep focus, memory recall, and cognitive enhancement—using binaural beats. But the real magic lies under the hood: they are both written entirely in Coni WASM.

Writing WebGL Shaders in Pure Coni Lisp!

In our previous post, we showcased the raw power of Coni WASM by orchestrating an 80,000 particle WebGL matrix. But there was always one lingering annoyance in the WebGL pipeline: the shaders themselves.

If you’ve ever written WebGL code, you know the drill. You end up writing your Vertex and Fragment shaders as massive, messy string literals concatenated across your codebase. You lose syntax highlighting, formatting, and—most tragically for Lisp hackers—you lose structural editing (Paredit/Slurp/Barf).

Building a Multi-City Flight Search App in WebAssembly with Coni and re-frame

The Coni WASM Revolution

One of the most exciting aspects of the Coni language is its seamless compilation to WebAssembly (WASM). To put this capability to the ultimate test, I recently built a Multi-City Flight Search App entirely in Coni.

Instead of writing vanilla JavaScript or pulling in massive NPM frameworks, this app uses a custom, lightweight port of the famous re-frame state management pattern—written purely in Coni!

Why re-frame in Coni?

If you’re familiar with ClojureScript, you already know the elegance of re-frame. It provides a unidirectional data flow and highly predictable state management. Bringing this pattern to Coni means we can write UI applications using:

Building Native Parallel Downloads in Coni

One of the great things about building your own language and tooling is the ability to rethink how operations are performed. In our latest commits, we decided to tackle a major bottleneck in our build process: downloading Maven dependencies.

The Problem with Platform-Specific Scripts

Previously, the download-url-to-file function in Coni relied on shelling out to platform-specific tools:

  • On Linux/macOS, it spawned a curl process.
  • On Windows, it invoked a massive powershell command using System.Net.WebClient.

While this worked, it had several drawbacks. First, shelling out to external processes is slow and resource-intensive. Second, the dependency on external tools meant that subtle differences in curl versions or Windows security protocols could cause unexpected failures. Most importantly, downloading artifacts sequentially using these shell commands meant that resolving a large Maven project would take entirely too long.

Supercharging the Coni CLI with Embedded Subcommands

One of the greatest strengths of the Coni language is its portability. We designed it so that the core interpreter and standard libraries are shipped as a single, static binary. You don’t need a bloated installation process; you just download the executable and you’re good to go.

However, as the ecosystem grew—like the addition of our Android build pipeline—we found ourselves with a minor workflow annoyance. To invoke the Android APK builder, you had to run the script via its absolute path:

compare_reviews.clj — A tiny, configurable LLM-powered reviewer in Clojure

Overview

This tool assembles Markdown from patterns, calls an LLM with a structured prompt, and writes a report as Markdown (and optionally PDF). It’s driven by EDN config, meaning you can change behavior by editing data, not code.

Why it’s cool:

  • Configuration-as-data (EDN): composable, Git-friendly, reproducible.
  • Clear pipeline: collect → call LLM → write → optional PDF and summary.
  • Smart outputs: timestamped filenames avoid collisions with minimal friction.
  • Extensible “agent” model: the LLM call is just a map merged into agent/call.
  • Minimal code, lots of leverage (Pandoc, glob patterns, simple IO).

High-level data flow


Code walkthrough (what each function does)

(ns margin-mania.reporting.compare-reviews
  (:require [clojure.edn :as edn]
            [pyjama.core :as agent]
            [margin-mania.reporting.utils :as mru]
            [pyjama.tools.pandoc]
            [clojure.java.io :as io])
  (:import (java.io File PushbackReader)
           (java.time LocalDateTime)
           (java.time.format DateTimeFormatter)))
  • pyjama.core/agent: abstraction over the LLM call (agent/call).
  • mru/aggregate-md-from-patterns: globs files and concatenates Markdown (plus optional metadata).
  • pyjama.tools.pandoc: converts Markdown to PDF.

load-config

(defn load-config [cfg]
  (cond
    (string? cfg)
    (with-open [r (io/reader cfg)]
      (edn/read (PushbackReader. r)))

    (map? cfg) cfg

    :else (throw (ex-info "Unsupported config type" {:given cfg}))))
  • Accepts either a path to EDN or a pre-built map.
  • Encourages configuration-as-data and REPL ergonomics.

Timestamp and output resolution

(defn ^:private timestamp []
  (.format (LocalDateTime/now)
           (DateTimeFormatter/ofPattern "yyyy-MM-dd_HH-mm-ss")))

(defn resolve-output-file
  "Return the actual File to write to.
   If out-file is a directory or has no extension, use <dir>/<yyyy-MM-dd_HH-mm-ss>.md."
  [out-file]
  (let [f (io/file out-file)
        as-dir? (or (.isDirectory f)
                    (not (re-find #"\.[^/\\]+$" (.getName f))))]
    (if as-dir?
      (io/file f (str (timestamp) ".md"))
      f)))
  • If :out-file is a directory or lacks an extension, it auto-generates a timestamped filename, e.g. 2025-08-27_16-30-12.md.

Summary file helper

(defn ^:private summary-file
  "Given the primary output file, return the summary file: <same path> with `_summary.md`."
  ^File [^File final-file]
  (let [parent (.getParentFile final-file)
        name (.getName final-file)
        base (if (re-find #"\.md$" name)
               (subs name 0 (- (count name) 3))
               name)]
    (io/file parent (str base "_summary.md"))))
  • Takes the main report path and returns the companion summary filename (e.g., report.mdreport_summary.md).

The main engine: process-review

(defn process-review
  "If :summary true, performs a second LLM call over the first call's output and writes
   `<previous out-file>_summary.md`."
  [{:keys [patterns model out-file system pre pdf summary]}]
  (let [combined-md (mru/aggregate-md-from-patterns patterns)
        result-1 (agent/call
                   (merge model
                          {:system system
                           :pre    pre
                           :prompt [combined-md]}))
        final-file (resolve-output-file out-file)
        out-1-str (with-out-str (println result-1))]
    ;; write main result
    (io/make-parents final-file)
    (spit final-file out-1-str)

    ;; optional PDF for main result
    (when pdf
      (pyjama.tools.pandoc/md->pdf
        {:input  final-file
         :output (str final-file ".pdf")}))

    ;; optional summary step
    (when summary
      (let [sum-pre "Generated a short summary, (with title and points just like a ppt slide)  of %s"
            result-2 (agent/call
                       (merge model
                              {:system system
                               :pre    sum-pre
                               :prompt [out-1-str]}))
            sum-file (summary-file final-file)]
        (io/make-parents sum-file)
        (spit sum-file (with-out-str (println result-2)))))

    ;; return the path(s) for convenience
    {:out     (.getPath final-file)
     :summary (when summary (.getPath (summary-file final-file)))
     :pdf     (when pdf (str final-file ".pdf"))}))
  • Aggregates input Markdown per :patterns, then calls the LLM once to produce the main report.
  • Writes the main .md, and if :pdf true, renders a PDF via Pandoc.
  • If :summary true, performs a second LLM call on the first output and writes ..._summary.md.
  • Returns a map of produced paths for convenience.

Notes: