explorable

The Explorable Library

Explorable makes a document explorable — turning it into a space where the reader can adjust assumptions, pose alternatives, and watch conclusions follow in real time.

The library makes this possible without asking much of the author. Inputs, outputs, and the relationships between them are declared directly in the document. The library reads those declarations and takes care of the rest — keeping values current, formatting them appropriately, showing and hiding content as conditions change, and making every input something a reader can naturally reach for and adjust.

Everything is designed to stay out of the way. The library has no opinions about how a document looks. It handles behaviour and leaves appearance entirely to the stylesheet. Extensions can be added without touching the core. And the document itself remains readable as a plain page even if the library never loads.

Core

This file has one job: to present the library's public face.

It doesn't contain any logic of its own. It simply gathers the pieces that outside code needs to interact with — the store, the registry, the engine's initialisation functions — and offers them from a single, stable location. Everything else can shift and reorganise internally without that affecting the way the library is consumed from outside.

Sources
Type File
module src/core.mjs

Base

This is the foundation everything else is built on — the one part of the library that every other part depends on, and the only place they all meet.

It carries two responsibilities. The first is memory: a single, authoritative record of every live value in the document. Inputs write into it, calculated results flow through it, and anything that wants to know the current state of the document comes here to ask. Nothing reads values back out of the visible page — this record is the truth, and the page is just its reflection.

The second responsibility is the registry: the place where every extension to the library announces its existence before the document loads. Every kind of helper, every formatting rule, every interaction behaviour, every condition operator — all declare themselves here first. The registry holds those declarations without acting on them, making them available to the rest of the system when the time comes.

These two things together — shared memory and a shared registry — are what allow the different parts of the library to remain independent from one another. They don't need to know about each other. They only need to know about this.

Sources
Type File
module src/core/base.mjs
global global/core/base.js

Engine

The engine is where declared intentions become live behaviour.

By the time the engine starts, every extension has already registered its capabilities, and every input has a known value. The engine's job is to wire those things together — to look at every calculated output in the document, understand what it depends on, and make sure it stays current whenever any of those dependencies change. From that point on, the document is alive: adjust an input, and everything that flows from it updates instantly and in the right order.

That last detail — order — is something the engine handles quietly but carefully. A document can have outputs that depend on other outputs, not just on raw inputs. The engine maps these relationships before anything runs, and ensures that no output ever evaluates before the values it relies on are already settled. The author never has to think about this.

The engine also takes responsibility for how calculated values are presented. A raw number is one thing; a number formatted as a currency, a percentage, or a date is another. The engine applies those presentation rules automatically, and signals each output when its value has changed — giving the rest of the system, and the stylesheet, the opportunity to respond.

Sources
Type File
module src/core/engine.mjs
global global/core/engine.js

Conditions

The conditions system answers a single question on behalf of any output element in the document: should I be visible right now?

Every output that carries a condition is essentially making a claim — show me when this situation is true — and the conditions system is what decides, at any moment, whether that claim holds. When it does, the output becomes active. When it doesn't, it steps back. This happens continuously and automatically as values in the document change.

Values enter the document through inputs, flow through the engine's calculations, and the conditions system sits at the far end of that journey — watching the resulting state and deciding which conclusions surface to the reader. It never changes a value. It only listens, and responds.

Sources
Type File
module src/core/conditions.mjs
global global/core/conditions.js

Examples

Numeric equality

Simple case. A hidden constant, a range input. Show message only when the value exactly matches the constant.

Properties
Type Property
operator eq

Value:

Exactly . Well done.

Numeric range

Show message only when value is between 40 and 60 inclusive.

Properties
Type Property
operator and
gte
lte

Value:

Within range (). Below minimum (). Above maximum ().

Either condition met

Two inputs. Show message if either equals its threshold.

Properties
Type Property
operator eq
or
helper alias

A:

B:

At least one value hit its threshold (A= or B=).

String equality

A select and a hidden string constant. data-type="string" ensures no numeric coercion happens.

Properties
Type Property
operator eq
neq
helper alias

What's your name?

Hello, . It really is . You are not .

Boolean data-type

A checkbox-like pattern using a select for boolean state, compared against a boolean constant.

Properties
Type Property
operator eq
neq
type boolean

Are you a member?

Member discount applied. No discount. Join to save.

Condition on computed output

Condition on a computed output. Total savings must exceed threshold before the reward message appears.

Properties
Type Property
operator gte
lt
helper alias
multiply
formatter currency

Weekly saving: × weeks =

Goal of reached. You saved enough to make it happen. Not there yet. Keep going.

Conditions Plugin

The core conditions system ships with operators built around numerical relationships — equal, greater than, lesser than, and so on. This plugin extends that vocabulary with operators that reason about text: whether a value contains a certain string, starts with it, or ends with it.

It is a narrow extension with a clear purpose, and follows the same pattern as every other plugin: declare, register, and step back.

Sources
Type File
module src/plugins/conditions.mjs
global global/plugins/conditions.js

Examples

Text search

A text input compared against a hidden string constant. Three operators, each surface their own message independently. All comparisons are case-insensitive.

Properties
Type Property
operator contains
endsWith
startsWith
helper alias
type string

Steve Jobs' favourite food must have been .

Contains . Starts with . Ends with .

Formatters Plugin

Formatters are what stand between a raw value and a reader-friendly representation of it.

A number on its own carries no context. The formatters plugin is what turns it into a price in a specific currency, a percentage, a nicely punctuated figure, or a human-readable date — shaped for the language and conventions of whoever is reading. The locale is picked up automatically from the document itself, so the same formatter produces the right result whether the document is in English, German, or anything else.

Formatters can apply to live, reactive values that update as the document changes, and equally to static values that are fixed at load time. In both cases the principle is the same: the raw value is the truth, and the formatted text is just how it is shown.

Sources
Type File
module src/plugins/formatters.mjs
global global/plugins/formatters.js

Examples

Static formatted values

Fixed values formatted at load time. No inputs, no reactivity — just raw numbers and dates presented in a form fit for reading.

Properties
Type Property
formatter currency
date
number
percentage

? was Tax Day in the US. For a ? annual salary in ?, the estimated federal income tax is approximately ? for a single filer with no dependents, resulting in a net federal tax liability of ? after (? up to the wage base) Social Security and (?) Medicare deductions.

Helpers Plugin

Helpers are named formulas that outputs can call on to derive their value from one or more inputs.

The engine ships with a small set of built-in helpers covering basic arithmetic — adding, subtracting, multiplying, dividing, and passing a value through unchanged. These handle the common cases. The helpers plugin exists for everything beyond that: domain-specific calculations that a particular document needs but that don't belong in the core.

Adding a new helper is a matter of giving it a name and describing what it does with the values it receives. Once registered, it becomes available to any output in any document that wants to use it, by name.

Sources
Type File
module src/plugins/helpers.mjs
global global/plugins/helpers.js

Examples

Slug from text inputs

Two text inputs combined and slugified. The result updates as either value changes, using a configurable separator.

Properties
Type Property
helper slugify

Title:

Tag:

Slug:

Visuals Plugin

Visuals are how an input's value can express itself not just as text, but as appearance.

Where formatters translate a value into a readable string, visuals translate it into something the stylesheet can act on — a colour, a state, a signal. The visual itself doesn't decide how anything looks; it simply communicates the current value to the CSS in a form it can use. What actually happens on screen is entirely the stylesheet's decision.

This keeps the boundary clean: the library handles the when and the what, the stylesheet handles the how.

Sources
Type File
module src/plugins/visuals.mjs
global global/plugins/visuals.js

Examples

Color swatch

A color input whose chosen value is passed to the stylesheet as a CSS custom property. The label reflects it visually; the library only communicates the value.

Properties
Type Property
visual color

Pick a color:

Main

This is the entry point, the orchestrator, the file that sets everything in motion.

It has no logic of its own either, but unlike Core its job is sequence rather than surface. It calls each part of the library to life in the right order: plugins first, so their capabilities are registered before anything tries to use them; then the engine, then the interaction layer. By the time it is done, the document is fully alive.

Sources
Type File
module src/main.mjs
global global/main.js

Types

This file has no presence at runtime — it never loads in the browser and plays no part in how the document behaves.

Its sole purpose is to describe the shapes of things: what a formatter looks like, what a condition expects, what the store offers. These descriptions exist entirely for the benefit of the author working in their editor — catching mismatches early, surfacing the right suggestions, and making the library's intentions legible without having to read through the implementation.