Magic Commons' interface kit. One set of tokens, materials, widgets, gestures, window chrome and a modulation system, as vanilla ES modules and plain CSS. No framework, no build step. An app adopts MIR: it copies the bytes in, never edits them, and asks for changes here.
MIR is the design system Josh built across two instruments — BASINS (the MANDELBROT museum), whose modulation window is MIR's ancestor, and λWAVES, where the kit was extracted on 2026-09-10. From here on MIR is the source; the apps are readers.
| Layer | Where | What it is |
|---|---|---|
| Tokens | mir/css/base.css (:root, theme blocks) |
Colour, spacing, type scale, radii, the accent pair A/B, light and dark. Nothing else names a colour. |
| Widgets | mir/css/base.css + mir/kit.js |
knob, seg, sw, trig, fader, readout, formula, device (window chrome), group, chip, gripDots, el. Each builds its own DOM and returns handles (set, get, setDisabled, setStatus…). |
| Materials | mir/css/skin.css |
The three card styles — tinted (a pane, no filter), refractive (blur only), frost (a policy over both) — the neumorphic seats, the hairlines, the three faces of a window. |
| Gestures | mir/kit.js, mir/slider-keys.js, mir/control-help.js, mir/modulation/curve-gesture.js |
The drag law — 220 px for a full scale, Shift for fine, 320 under a finger — as defaults an app may retune (setKnobLaw, dragTravel); arrows, Page, Home/End and Delete on every slider; hints that step aside for a hand on a control and show for the keyboard; the dot grip means drag me, the four-way cross means route me. Curve editors use FL Studio's point/tension gesture law from curve-gesture.js; hosts do not reinterpret it. |
| Scheduling | mir/window-activity.js |
Idle is zero work: a window that is closed, folded, powered off or off-screen does not compute. |
| Controls | mir/plane-model.js |
The sphere-and-plane orientation control. More belong here as they are built. |
| Palette | mir/palette.js |
OKLab, the WCAG contrast floor, the 256-entry palette LUT and the 23-palette catalogue the accents read. |
| Shell | mir/shell/ |
What every app begins with, taken node for node from λWAVES: the wordmark (wordmark.js), the menubar it opens — FILE · EDIT · VIEW · WINDOW · ABOUT as data (menubar.js), the notebook glass with NOTES and ABOUT faces (notebook.js, about.js — the GPL notice and the font licences by default), the accent engine that colours A, B and the mark from a palette (accent.js), their sheet (shell.css) and an optional ground (stage.css). NOTES previews markdown and maths with marked and KaTeX from vendor/, loaded on the first preview. See gallery/shell.js for the whole assembly. |
| Modulation | mir/modulation/ |
The kit's first plugin: the modulation window's builders (modwindow/, BASINS' window, the source since 1.1.0), its host (host.js, modhost.css), model (mod.js), registry, curves and the shared FL-style curve gesture interpreter (curve-gesture.js). The rest of the controller that wires the window to a host is still λWAVES' (lab/modwindow.js). See modulation/modwindow/host-contract.md and docs/PLUGIN-CONTRACT.md. |
| Type | fonts/ |
LW Title (a renamed Spinwerad subset), Roboto (UI), STIX Two Math (the maths), with their licences. |
| Laws | docs/ |
API (every export) · CONTRACT (load order, what the kit reads on <body>, the tokens an app may re-point, ids, storage) · PLUGIN-CONTRACT (the socket TIMELINE plugs into) · STYLE-LOCK, MOTION-LAW, ANTI-PATTERNS, REFERENCES — the reasoning, kept with the code. |
| Proofs | tests/, tools/ |
npm test: the token lint, node tests of the model and the adopt tool, browser tests of the controls and the shell. tools/stylehash.mjs proves a kit change neutral in an app; tools/shell-parity.mjs proves the shell is λWAVES'. |
node tools/adopt.mjs ../my-app # copies mir/ → my-app/lab/mir/, fonts/ → my-app/lab/fonts/, removes what the kit no longer has, writes MIR-MANIFEST.json
node tools/adopt.mjs ../my-app --check # changes nothing; exit 1 on a changed, missing or extra kit file, or a manifest that lies
node tools/adopt.mjs ../my-app --dry-run # what an adopt would add, update and remove
# --prefix <dir> puts the kit somewhere other than lab/; --allow-dirty adopts from uncommitted kit changes (the manifest says so)In the app's index.html, in this order, before any sheet of the app's own:
<link rel="stylesheet" href="./mir/css/base.css">
<link rel="stylesheet" href="./mir/css/skin.css">
<link rel="stylesheet" href="./mir/shell/shell.css"> <!-- if the app uses the shell (wordmark, menubar, notebook) -->
<link rel="stylesheet" href="./mir/shell/stage.css"> <!-- optional: λWAVES' ground, if the app has none of its own -->
<!-- the app's own sheets here -->
<link rel="stylesheet" href="./mir/modulation/modhost.css"> <!-- only if the app uses modulation -->
<link rel="stylesheet" href="./mir/modulation/modwindow/modwindow.css"> <!-- and this one LAST: nothing may follow it -->import { knob, seg, trig, device, gripDots } from './mir/kit.js';
import { installControlHelp } from './mir/control-help.js';
// the shell
import { wordmark } from './mir/shell/wordmark.js';
import { createMenubar } from './mir/shell/menubar.js';
import { createNotebook } from './mir/shell/notebook.js';
import { createAccent } from './mir/shell/accent.js';A fresh app's shell is five calls — the wordmark, the accent, the menus as data, the notebook with its ABOUT words.
The shell keeps λWAVES' gutter for a rack on each side; an app with no racks sets --rack-w: 0px in its own sheet.
One menubar and one notebook per page (they own the ids menubar and notebook); both have destroy().
const title = wordmark(stage, { word: 'MYAPP' });
const accent = createAccent(); // A 30°, B 300° on the palette; pass { a: 60, b: 300 } for Josh's law
const menus = createMenubar({ opener: title, host: lab, menus: { FILE: () => [['NEW'], null, ['EXPORT (.json)']], ABOUT: () => [['ABOUT MYAPP', () => notebook.open('about')]] } });
const notebook = createNotebook({ host: stage, name: 'MYAPP', about: { version: '0.1.0', tagline: 'What it is, in one sentence.', copyright: '© 2026 …' }, onLogo: () => accent.paintMarks() });
accent.apply();Reuse the nodes, the gestures and the CSS. Do not copy their look. A widget the kit has is built by the
kit's builder and styled by the kit's sheet. When something is missing, add it to MIR (with its law and its
proof) and re-adopt; do not grow a second version inside an app. PROMPT.md is the sentence to give a model
starting a new app.
The look MIR has today is called FROST — frosted glass over the stage, the VST-LFO modulation window's material — and it is the only skin. Skins are coming: a skin will be a file a user can download (JSON) that changes the fonts, the buttons, the windows, everything; every skin has a Dark and a Light variant and works with the accent A/B system; and SETTINGS is where one is chosen. The second skin planned is METRO (flat, colour switches instead of shading — old Android material and the Windows 8–10 start-menu cards). Until skins land, structure and look share one set of sheets; the split comes with METRO, because a second skin is the only proof that the split is real.
npm run gallery serves http://127.0.0.1:8790/gallery/ — every token, control, window state, glyph and the
modulation window on one page, built from the kit itself and restyled by nothing, with the theme, card style,
frost, ground (plain or a busy coloured field) and accent seats live.
gallery/shell.html is a fresh app on the kit: hover the wordmark for the menus, press J for the notebook, ⓘ for ABOUT.
npm test # token lint · node tests (adopt, registry, curve, host, model) · browser tests (controls, shell)
node tools/lint-tokens.mjs --unused # every token read has a writer; and what is written but never readNeutrality — does a kit change change what an app draws? Adopt the working kit into a copy of the app (never the app itself), serve both, then:
node tools/stylehash.mjs capture before http://127.0.0.1:8811/index.html out # the app as it ships (twice: the second is the noise)
node tools/stylehash.mjs capture before2 http://127.0.0.1:8811/index.html out
node tools/stylehash.mjs capture after http://127.0.0.1:8812/index.html out # the copy on the new kit
node tools/stylehash.mjs compare out before after --noise before2 # every visible element × 8 states; exit 1 on a difference(--theme, --card, --frost, --ready, --setup tell it how to drive the app; --gpu 1 for an app that needs WebGPU.
Measure an app FULLY booted: λWAVES builds its help and native controls only once the GPU is up, so drive it with
--gpu 1 --ready "__LW.ready && !!document.getElementById('controlHelp')" --theme "__LW.setTheme(%s)" --card "__LW.setCardStyle(%s)" --frost "__LW.setFrost(%s ? 'always' : 'off')" —
a half-booted page is a half proof.)
The shell is λWAVES' shell — serve λWAVES' lab/ and this repository, then
node tools/shell-parity.mjs capture lambdawaves http://127.0.0.1:8779/index.html /tmp/parity
node tools/shell-parity.mjs capture mir-as-lw 'http://127.0.0.1:8780/gallery/shell.html?as=lambdawaves' /tmp/parity
node tools/shell-parity.mjs compare /tmp/parity lambdawaves mir-as-lw # styles, text, boxes, pixels · 14 states · exit 1 on any difference
node tools/shell-behaviour.mjs http://127.0.0.1:8780/gallery/shell.html # 19 behaviour probes · exit 1 on any failureThey need Chromium (CHROMIUM=…; otherwise the first on PATH, then the snap's) and nothing else.
MIR was extracted from λWAVES by AI coding agents (Claude Code with Anthropic's Claude models; parts of the
modulation window by OpenAI's Codex; design reviews by Google's Gemini) under Joshua Hosain's direction. He
is not a programmer and says so; the λWAVES README
carries the full disclosure — what was decided by hand, what was verified, what was not — and the same terms
apply here. Contributions made the same way are welcome under λWAVES' AI_POLICY.md.
GNU GPL v3.0 only — © 2026 Joshua Hosain, Magic Commons. Third-party notices ride with the fonts and the modulation window's provenance notes.