#!/usr/bin/env python3 """Muse Tour de Force — HTML renderer. Takes the structured TourData dict produced by tour_de_force.py and renders a self-contained, shareable HTML file with an interactive D3 commit DAG, operation log, architecture diagram, and animated replay. Stand-alone usage ----------------- python tools/render_html.py artifacts/tour_de_force.json python tools/render_html.py artifacts/tour_de_force.json --out custom.html """ from __future__ import annotations import json import pathlib import sys import urllib.request # --------------------------------------------------------------------------- # D3.js fetcher # --------------------------------------------------------------------------- _D3_CDN = "https://cdn.jsdelivr.net/npm/d3@7.9.0/dist/d3.min.js" _D3_FALLBACK = f'' def _fetch_d3() -> str: """Download D3.js v7 minified. Returns the source or a CDN script tag.""" try: with urllib.request.urlopen(_D3_CDN, timeout=15) as resp: src = resp.read().decode("utf-8") print(f" ↓ D3.js fetched ({len(src)//1024}KB)") return f"" except Exception as exc: print(f" ⚠ Could not fetch D3 ({exc}); using CDN link in HTML") return _D3_FALLBACK # --------------------------------------------------------------------------- # Architecture SVG # --------------------------------------------------------------------------- _ARCH_HTML = """\
muse CLI
14 commands
init · commit · log · diff · show · branch
checkout · merge · reset · revert · cherry-pick
stash · tag · status
Plugin Registry
resolve_plugin(root)
Core Engine
DAG · Content-addressed Objects · Branches · Store · Log Graph · Merge Base
MuseDomainPlugin Protocol
Implement 6 methods → get the full VCS for free
MusicPlugin
reference impl
MIDI · notes · CC · pitch
GenomicsPlugin
planned
sequences · variants
SpacetimePlugin
planned
3D fields · time-slices
YourPlugin
implement 6 methods
get VCS for free
Method
Signature
Purpose
snapshot
snapshot(live_state) → StateSnapshot
Capture current state as a content-addressable JSON blob
diff
diff(base, target) → StateDelta
Compute minimal change between two snapshots (added · removed · modified)
merge
merge(base, left, right) → MergeResult
Three-way reconcile divergent state lines; surface conflicts
drift
drift(committed, live) → DriftReport
Detect uncommitted changes between HEAD and working state
apply
apply(delta, live_state) → LiveState
Apply a delta during checkout to reconstruct historical state
schema
schema() → DomainSchema
Declare data structure — drives diff algorithm selection per dimension
""" # --------------------------------------------------------------------------- # HTML template # --------------------------------------------------------------------------- _HTML_TEMPLATE = """\ Muse — Tour de Force

muse

Tour de Force · domain-agnostic version control for multidimensional state v{{VERSION}} · {{DOMAIN}} domain · {{ELAPSED}}s Domain Registry →
{{COMMITS}}Commits
·
{{BRANCHES}}Branches
·
{{MERGES}}Merges
·
{{CONFLICTS}}Conflicts Resolved
·
{{OPS}}Operations

Commit Graph

Operation Log

Dimension State Matrix

Unlike Git (binary file conflicts), Muse merges each orthogonal dimension independently — only conflicting dimensions require human resolution.
Melodic
Rhythmic
Harmonic
Dynamic
Structural
Conflict (required resolution)
Unchanged
⚡ Merge conflict (shared-state.mid) — shared-state.mid had both-sides changes in structural (manual resolution required). ✓ melodic auto-merged from left · ✓ harmonic auto-merged from right — only 1 of 5 dimensions conflicted. Git would have flagged the entire file as a conflict.

How Muse Works

Muse is a version control system for state — any multidimensional state that can be snapshotted, diffed, and merged. The core engine provides the DAG, content-addressed storage, branching, merging, time-travel, and conflict resolution. A domain plugin implements 6 methods and gets everything else for free.

Music is the reference implementation. Genomics sequences, scientific simulation frames, 3D spatial fields, and financial time-series are all the same pattern.

{{ARCH_HTML}}

Domain Plugin Registry

Every domain registered with Muse appears here. Each plugin implements the six-method MuseDomainPlugin protocol and gets the full VCS — branching, merging, conflict resolution, time-travel, and diff — for free. Scaffold a new domain with a single command.

CRDT Primitives

Plugins that implement CRDTPlugin get four convergent data structures that merge without coordination. Any two replicas always converge to the same state — no central authority required.

{{D3_SCRIPT}} """ # --------------------------------------------------------------------------- # Main render function # --------------------------------------------------------------------------- def render(tour: dict, output_path: pathlib.Path) -> None: """Render the tour data into a self-contained HTML file.""" print(" Rendering HTML visualization...") d3_script = _fetch_d3() meta = tour.get("meta", {}) stats = tour.get("stats", {}) # Format generated_at nicely gen_raw = meta.get("generated_at", "") try: from datetime import datetime, timezone dt = datetime.fromisoformat(gen_raw).astimezone(timezone.utc) gen_str = dt.strftime("%Y-%m-%d %H:%M UTC") except Exception: gen_str = gen_raw[:19] html = _HTML_TEMPLATE html = html.replace("{{VERSION}}", str(meta.get("muse_version", "0.1.1"))) html = html.replace("{{DOMAIN}}", str(meta.get("domain", "music"))) html = html.replace("{{ELAPSED}}", str(meta.get("elapsed_s", "?"))) html = html.replace("{{GENERATED_AT}}", gen_str) html = html.replace("{{COMMITS}}", str(stats.get("commits", 0))) html = html.replace("{{BRANCHES}}", str(stats.get("branches", 0))) html = html.replace("{{MERGES}}", str(stats.get("merges", 0))) html = html.replace("{{CONFLICTS}}", str(stats.get("conflicts_resolved", 0))) html = html.replace("{{OPS}}", str(stats.get("operations", 0))) html = html.replace("{{ARCH_HTML}}", _ARCH_HTML) html = html.replace("{{D3_SCRIPT}}", d3_script) html = html.replace("{{DATA_JSON}}", json.dumps(tour, separators=(",", ":"))) output_path.write_text(html, encoding="utf-8") size_kb = output_path.stat().st_size // 1024 print(f" HTML written ({size_kb}KB) → {output_path}") # --------------------------------------------------------------------------- # Stand-alone entry point # --------------------------------------------------------------------------- if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Render tour_de_force.json → HTML") parser.add_argument("json_file", help="Path to tour_de_force.json") parser.add_argument("--out", default=None, help="Output HTML path") args = parser.parse_args() json_path = pathlib.Path(args.json_file) if not json_path.exists(): print(f"❌ File not found: {json_path}", file=sys.stderr) sys.exit(1) data = json.loads(json_path.read_text()) out_path = pathlib.Path(args.out) if args.out else json_path.with_suffix(".html") render(data, out_path) print(f"Open: file://{out_path.resolve()}")