Skip to content

Architecture

browser-json-tree is a small PureScript library with a thin JavaScript interop layer. The whole thing is four source modules under src/, one foreign-function file, and one self-contained stylesheet.

Module layout

flowchart TD
    PSApp["PureScript / Halogen app"]
    JSApp["Vanilla JS app"]

    subgraph src["src/JsonTree"]
        Render["JsonTree<br/>render / renderWith / Config / LinkSpec"]
        Cardano["JsonTree.Cardano<br/>resolve + shape detectors"]
        Behaviour["JsonTree.Behaviour<br/>install :: Effect Unit (foreign import)"]
        BehaviourJS["Behaviour.js<br/>click / dblclick / copy handlers"]
        JSAdaptor["JsonTree.JS<br/>mount / mountFromString / install"]
    end

    Bundle["dist/browser-json-tree.js<br/>spago bundle of JsonTree.JS"]
    CSS["dist/json-tree.css<br/>--jt-* themable styles"]

    PSApp --> Render
    PSApp --> Behaviour
    JSApp --> Bundle
    Bundle --> JSAdaptor
    JSAdaptor --> Render
    JSAdaptor --> Behaviour
    Render --> Cardano
    Behaviour --> BehaviourJS
    Render -.->|"emits .v-* classes"| CSS
    Behaviour -.->|"toggles open / .v-copy--ok"| CSS
Module File Responsibility
JsonTree src/JsonTree.purs The renderer. render / renderWith, the Config and LinkSpec types, and defaultConfig. Walks a Json and emits Halogen.HTML.
JsonTree.Cardano src/JsonTree/Cardano.purs The default string-leaf resolver and its shape detectors (isTxidHex, isBech32Addr, isPolicyHex, parseTxin, shortHex, shortAddr).
JsonTree.Behaviour src/JsonTree/Behaviour.purs + Behaviour.js A single install :: Effect Unit foreign import that attaches the document-level click / dblclick / copy listeners.
JsonTree.JS src/JsonTree/JS.purs Vanilla-JS adaptor. mount / mountFromString / install, bundled to dist/browser-json-tree.js for non-PureScript consumers.

JsonTree.Cardano mirrors the LinkSpec record type locally rather than importing it from JsonTree, which keeps the dependency edge one-directional (JsonTree → JsonTree.Cardano) and avoids a cyclic module reference.

Render pipeline

render is renderWith defaultConfig, and renderWith is renderValue, which dispatches on the six Json constructors via Data.Argonaut.Core.caseJson:

flowchart LR
    Json["Json value"] --> Case["renderValue<br/>(caseJson)"]
    Case -->|"null / bool / number"| Prim["primitive span<br/>.v-null / .v-bool / .v-num"]
    Case -->|"string"| Str["cfg.resolveString s"]
    Str -->|"Just LinkSpec"| Link["truncated &lt;a&gt; link"]
    Str -->|"Nothing"| Plain[".v-str text leaf"]
    Case -->|"array"| Arr["&lt;ol class=v-array&gt;<br/>per-item index bracket"]
    Case -->|"object"| Obj["&lt;div class=v-object&gt;<br/>per-entry &lt;details&gt;"]
    Arr -->|"each item"| Case
    Obj -->|"each entry"| Case
  • Compound nodes (non-empty arrays and objects) render inside a native <details>; the key (.v-key-toggle) or array index marker (.v-sep-toggle) is the <summary>. Leaf nodes render inline.
  • hideEmpty drops object entries whose value is null, [], or {} before rendering (default on).
  • initiallyOpen sets the HTML open attribute on every compound node (default off — readers drive the unfold).
  • String leaves pass through cfg.resolveString. Just LinkSpec becomes a truncated <a target="_blank" rel="noopener"> with the full value on the title tooltip; Nothing becomes a plain .v-str span.

Interaction model

Native <details> already gives single-click toggle. Behaviour.js adds what it does not:

  • Single click on a .v-key-toggle / .v-sep-toggle summary toggles that one level; on close it cascades open = false to all descendant <details> so the next reopen starts one level deep.
  • Double click forces the whole subtree open (cascade(_, true)) and clears the text selection — the gesture commits to "show me everything" and never collapses.
  • Copy fires in the capture phase for any .v-copy element with a data-copy attribute: it writes the value to navigator.clipboard and briefly flashes .v-copy--ok. Capture phase stops the click from bubbling to a surrounding <summary>.

The summary handlers filter on the two toggle classes, so other native <details> on the host page keep their stock behaviour.

Build outputs

Everything is driven from flake.nix (nix/project.nix) and mirrored by just recipes:

Output Built by What it is
library spago build --offline Compile-check of the library; distributed as source.
jsBundle spago bundle --module JsonTree.JS The standalone ES module dist/browser-json-tree.js (~180 KB, Halogen runtime included).
example spago bundle --module Main (in examples/) The Halogen demo app, also an end-to-end smoke that the public surface stays reachable.
docsSite mkdocs build --strict This documentation site, with the JS bundle, the demo, and json-tree.css copied into docs/assets/.

nix flake check runs all four derivations plus a purs-tidy lint gate. On a v* tag, .github/workflows/release.yml publishes a source tarball alongside the standalone browser-json-tree-<version>.js and json-tree-<version>.css assets.