From 825c1d2fa563f5de974a64b5346d07f9200cafac Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 16 May 2026 17:39:49 +0100 Subject: [PATCH 1/2] refactor(ui): static HTML/JS UI; README->AsciiDoc; launcher + container - Remove the ReScript SPA (ui/src/App.res, ui/rescript.json, ui/deno.json) - Replace with vanilla static UI (ui/public/app.js, reworked index.html/styles.css) - Add ui/launcher/, ui/INSTALL.sh, ui/README.adoc, playground/README.adoc - Add containers/Containerfile.chainguard - Convert README headings from Markdown to AsciiDoc WIP snapshot committed during the 2026-05-16 estate sync (rebased onto current upstream main). Co-Authored-By: Claude Opus 4.7 (1M context) --- README.adoc | 369 ++++++++++++++-- containers/Containerfile.chainguard | 105 +++++ playground/README.adoc | 445 +++++++++++++++++++ ui/INSTALL.sh | 534 ++++++++++++++++++++++ ui/README.adoc | 304 +++++++++++++ ui/deno.json | 23 - ui/launcher/README.adoc | 398 +++++++++++++++++ ui/launcher/betlang-playground.sh | 400 +++++++++++++++++ ui/launcher/keepopen.sh | 178 ++++++++ ui/public/app.js | 564 ++++++++++++++++++++++++ ui/public/index.html | 129 +++++- ui/public/styles.css | 657 +++++++++++++++++++++------- ui/rescript.json | 22 - ui/src/App.res | 304 ------------- 14 files changed, 3877 insertions(+), 555 deletions(-) create mode 100644 containers/Containerfile.chainguard create mode 100644 playground/README.adoc create mode 100755 ui/INSTALL.sh create mode 100644 ui/README.adoc delete mode 100644 ui/deno.json create mode 100644 ui/launcher/README.adoc create mode 100755 ui/launcher/betlang-playground.sh create mode 100755 ui/launcher/keepopen.sh create mode 100644 ui/public/app.js delete mode 100644 ui/rescript.json delete mode 100644 ui/src/App.res diff --git a/README.adoc b/README.adoc index 5df0ff5..d12f028 100644 --- a/README.adoc +++ b/README.adoc @@ -10,7 +10,7 @@ At its heart is a single idea: --- -## Core Concept +== Core Concept The fundamental primitive is the ternary form: @@ -30,7 +30,7 @@ This makes BetLang closer to a **probabilistic computer algebra system (CAS)** t --- -## What BetLang *Is* +== What BetLang *Is* * A **Symbolic Probabilistic Metalanguage (SPML)** * A **Probabilistic CAS** for uncertainty-aware computation @@ -38,7 +38,7 @@ This makes BetLang closer to a **probabilistic computer algebra system (CAS)** t * A **hosted language in Racket** with formalizable semantics * A system with a **rich uncertainty-aware number tower (14 systems)** -## What BetLang *Is Not* +== What BetLang *Is Not* * Not a DeFi or gambling language * Not just a Monte Carlo scripting tool @@ -46,11 +46,11 @@ This makes BetLang closer to a **probabilistic computer algebra system (CAS)** t --- -## v1.0 Architecture +== v1.0 Architecture BetLang is a **multi-layer system** with clearly separated responsibilities: -### Racket — Authoritative Frontend / Specification +=== Racket — Authoritative Frontend / Specification * `#lang betlang` defines the language * `syntax-parse` + nanopass for IR and transformations @@ -59,9 +59,7 @@ BetLang is a **multi-layer system** with clearly separated responsibilities: This is the **source of truth** for semantics. ---- - -### Julia — Compute Kernel +=== Julia — Compute Kernel * High-performance execution backend * Primary path for numerical and statistical workloads @@ -78,9 +76,7 @@ This is the **source of truth** for semantics. Over time, the full **uncertainty number tower migrates here**. ---- - -### Lean 4 — Proof / Export Layer +=== Lean 4 — Proof / Export Layer * Used for: @@ -91,9 +87,7 @@ Over time, the full **uncertainty number tower migrates here**. This layer ensures BetLang can become **formally trustworthy**. ---- - -### Rust — Optional Tooling Layer +=== Rust — Optional Tooling Layer * Status: **Paused / Non-core** * Intended for: @@ -106,33 +100,33 @@ Rust is explicitly **not part of core semantics**. --- -## Core Features +== Core Features -### Ternary Computation +=== Ternary Computation * `(bet A B C)` — primitive choice * `(bet/weighted ...)` — non-uniform probabilities * `(bet_conditional ...)` — predicate-driven selection -### Lazy Semantics +=== Lazy Semantics * Only the chosen branch is evaluated * Enables symbolic and infinite structures * Prevents unnecessary computation -### Compositionality +=== Compositionality * Bets compose like algebraic objects * Supports chaining, mapping, folding, and higher-order composition --- -## The Real Moat: Uncertainty-Aware Number Systems +== The Real Moat: Uncertainty-Aware Number Systems BetLang includes **14 distinct number systems** for representing uncertainty: * Gaussian distributions - n- Interval / affine arithmetic +* Interval / affine arithmetic * Fuzzy numbers * Bayesian numbers * Risk-based numbers (VaR / CVaR) @@ -147,7 +141,7 @@ See: `docs/number-tower.md` --- -## Semantics +== Semantics BetLang distinguishes between: @@ -160,7 +154,92 @@ This distinction is critical and formalized in: --- -## Tooling Roadmap +== Interactive Playgrounds + +BetLang provides two playground environments for exploration and experimentation: + +=== Web-Based Quantum Playground (`ui/`) + +A **quantum-inspired interactive web environment** for exploring Betlang's probabilistic model. + +* **Location**: `ui/` directory +* **Features**: + + * Code editor with syntax hints + * 8 quantum-analog examples (superposition, entanglement, measurement) + * Probability distribution histograms (SVG) + * Ternary bet tree visualization + * Configurable sample counts (1-100,000) + * Dark/light theme switching + * History navigation +* **Technology**: ReScript + rescript-tea + Vite + Deno +* **URL**: https://betlang.org/playground (when deployed) + +See: link:ui/README.adoc[ui/README.adoc] for complete documentation. + +=== Experimental Playground (`playground/`) + +A **sandbox workspace** for FFI development, configuration, and experimental features. + +* **Location**: `playground/` directory +* **Features**: + + * FFI bindings (Zig, WASM planned) + * Project configuration (Nickel, Must, Just) + * Experimental code snippets + * RSR compliance checking + * Architecture documentation +* **Technology**: Deno, Zig, Nickel, Just + +See: link:playground/README.adoc[playground/README.adoc] for complete documentation. + +=== Shareable URLs + +Both playgrounds support **URL-encoded code sharing**: + +[source] +---- +# Share a specific snippet +https://betlang.org/playground#code= + +# Share with configuration +https://betlang.org/playground?theme=dark&samples=5000#code=... + +# Load specific example +https://betlang.org/playground?example=superposition +---- + +=== QR Code Generation + +Generate QR codes for physical sharing: + +[source,bash] +---- +# Using qrencode CLI +qrencode -t ANSIUTF8 "https://betlang.org/playground#code=$(echo 'bet {1,2,3}' | base64)" + +# Using Python +python -c "import qrcode; img = qrcode.make('https://betlang.org/playground#code=...'); img.save('betlang.png')" +---- + +=== Social Media Integration + +Share via popular platforms: + +[cols="2,3"] +|=== +| Twitter/X | `https://twitter.com/intent/tweet?text=Betlang%20Playground%20https://betlang.org/playground` +| Mastodon | `https://mastodon.social/share?text=Exploring%20Betlang%20https://betlang.org/playground` +| LinkedIn | `https://www.linkedin.com/sharing/share-offsite/?url=https://betlang.org/playground` +| Reddit | `https://www.reddit.com/submit?url=https://betlang.org/playground&title=Betlang%20Playground` +| Bluesky | `https://bsky.app/intent/compose?text=Check%20out%20Betlang%20https://betlang.org/playground` +| Discord | `https://discord.com/app?url=https://betlang.org/playground` +| Matrix | `https://matrix.to/#/#betlang:matrix.org?web-instance[element.io]=https://betlang.org/playground` +|=== + +--- + +== Tooling Roadmap Planned unified CLI: @@ -177,7 +256,87 @@ See: `docs/toolchain-roadmap.md` --- -## Example +== Ecosystem Integration + +=== K9 Contractiles + +K9 (Kennel-Yard-Hunt) **self-validating contractiles** provide configuration and deployment automation: + +[cols="1,2,2"] +|=== +| Level | Trust | Use Case + +| Kennel | High | Pure data, schemas, no execution +| Yard | Medium | Validated configuration with Nickel +| Hunt | Low | Full execution, requires signature +|=== + +* **Location**: `.machine_readable/svc/k9/` +* **Purpose**: Configuration management, deployment validation, security auditing + +=== ADR (Architecture Decision Records) + +All architectural decisions are documented as ADRs: + +* **ADR-001**: K9 service component organization +* **ADR-002**: RSR compliance enforcement +* **ADR-003**: Machine-readable metadata first + +* **Location**: `.machine_readable/svc/` + +=== Machine-Readable Metadata + +The project maintains extensive machine-readable metadata: + +[source] +---- +.machine_readable/ +├── 6a2/ +│ ├── AGENTIC.a2ml # AI agent constraints and permissions +│ ├── ECOSYSTEM.a2ml # Project ecosystem positioning +│ ├── META.a2ml # Project metadata and versioning +│ ├── NEUROSYM.a2ml # Neural-symbolic integration +│ ├── PLAYBOOK.a2ml # Operational procedures +│ └── STATE.a2ml # Current project state +└── svc/ + └── k9/ + └── README.adoc # K9 contractiles documentation +---- + +Each `.a2ml` file provides structured configuration for: + +* AI agent interactions and constraints +* Project positioning within the broader ecosystem +* Metadata versioning and updates +* Neural-symbolic integration patterns +* Operational playbooks and incident response +* Current state tracking + +=== AI Agent Integration (0-AI-MANIFEST.a2ml) + +The project includes comprehensive AI agent guidance: + +[source] +---- +0-AI-MANIFEST.a2ml # Universal AI Agent Gateway +├── Identity and project metadata +├── Critical invariants (banned languages, file locations) +├── Canonical file locations +├── Taxonomy index +└── Agent constraints +---- + +Key constraints for AI agents: + +* SCM files ONLY in `.machine_readable/` (root copies are symlinks) +* NEVER delete spec files: grammar, SPEC.core.scm +* NEVER use banned languages: TypeScript, Node.js, npm, Go, Python +* All GitHub Actions must be SHA-pinned +* All source files must have SPDX headers + +--- + +== Example ``` (bet 'win 'draw 'lose) @@ -197,7 +356,7 @@ Only one branch is evaluated. --- -## Use Cases +== Use Cases * Probabilistic programming * Bayesian inference @@ -207,10 +366,32 @@ Only one branch is evaluated. * Risk modeling * Scientific computing * Research in probabilistic semantics +* **Education** - Teaching probability and uncertainty concepts +* **Prototyping** - Quick experimentation with probabilistic models + +--- + +== Quantum-Analog Concepts + +For those familiar with quantum computing, BetLang provides analogous concepts: + +[cols="1,2,2"] +|=== +| Quantum Concept | BetLang Analog | Description + +| Qubit | Ternary Bet | `bet { |0>, |1>, |unknown> }` - three possible states +| Superposition | Weighted Bet | `bet { a @ w1, b @ w2, c @ w3 }` - probability amplitudes +| Entanglement | Nested Bets | `bet { x, y, bet { a, b } }` - correlated outcomes +| Measurement | Switch/Case | `switch result { | a => ..., | b => ... }` - collapse to value +| Interference | Probability Weights | Weighted combinations affect outcome distribution +| Unitary Evolution | Function Composition | Functions preserve probabilistic structure +|=== + +This mapping makes BetLang accessible to quantum programmers while maintaining its own semantic rigor. --- -## Philosophy +== Philosophy BetLang is built on three principles: @@ -220,16 +401,129 @@ BetLang is built on three principles: --- -## Status +== Status + +[cols="2,1,1"] +|=== +| Component | Status | Notes + +| Racket frontend | ✅ Authoritative | Canonical semantics +| Julia backend | 🟡 Active development | Primary compute kernel +| Lean integration | 🟡 Planned / partial | Proof layer +| Rust tooling | ⏸️ Optional / paused | Packaging, WASM +| Web Playground (ui/) | ✅ Available | Quantum-inspired features +| FFI (Zig) | 🟡 Experimental | playground/ffi/zig/ +| WASM backend | 🟡 Planned | compiler/bet-wasm/ +|=== + +--- + +== Project Structure + +[source] +---- +betlang/ +├── README.adoc # This file +├── LICENSE # PMPL-1.0-or-later +├── 0-AI-MANIFEST.a2ml # AI agent guidance +├── .machine_readable/ # Machine-readable metadata +│ ├── 6a2/ # Project state and config +│ └── svc/ # Service components +│ └── k9/ # K9 contractiles +├── compiler/ # Rust compiler components +│ ├── bet-wasm/ # WASM backend +│ └── ... +├── core/ # Racket core language +├── julia-backend/ # Julia compute kernel +├── ui/ # Web playground (quantum-inspired) +│ ├── public/ # Static assets +│ │ ├── index.html +│ │ └── styles.css +│ ├── src/ # ReScript source +│ │ └── App.res +│ ├── deno.json +│ ├── rescript.json +│ ├── vite.config.js +│ └── Justfile +├── playground/ # Experimental sandbox +│ ├── ARCHITECTURE.adoc +│ ├── README.adoc # Playground documentation +│ ├── Justfile +│ ├── config.ncl +│ ├── mustfile.toml +│ ├── deno.json +│ ├── rescript.json +│ ├── ffi/ # FFI bindings +│ │ └── zig/ +│ └── examples/ +└── docs/ # Documentation +---- + +--- + +== Getting Started + +=== Prerequisites + +* Racket (for language frontend) +* Julia (for compute backend, optional) +* Deno (for playground/web, optional) +* Rust (for compiler, optional) + +=== Quick Install + +[source,bash] +---- +# Clone the repository +git clone https://github.com/hyperpolymath/betlang.git +cd betlang + +# For Racket development +raco pkg install betlang + +# For web playground +git submodule update --init --recursive +cd ui +just dev + +# For experimental playground +cd playground +just check +---- + +=== Running Examples + +[source,bash] +---- +# Run conformance tests +racket -f conformance/smoke.bet + +# Run Julia backend examples +cd julia-backend/examples +julia coin-flip-game.bet + +# Start web playground +cd ui +just dev +---- + +--- + +== Contributing + +See link:CONTRIBUTING.md[CONTRIBUTING.md] for development guidelines. -* Racket frontend: **Authoritative** -* Julia backend: **Active development** -* Lean integration: **Planned / partial** -* Rust tooling: **Optional / paused** +Key principles: + +* All files must have SPDX license headers +* Machine-readable metadata must be kept in sync +* Changes must pass RSR compliance checks +* Follow ADR patterns for architectural decisions +* K9 contractiles for configuration changes --- -## License +== License BetLang uses **PMPL-1.0 (Palimpsest Public License)**. @@ -237,8 +531,19 @@ See `LICENSE` for the precise definition and terms. --- -## Closing +== Closing BetLang is not about betting. It is about **making uncertainty programmable**. + +--- + +== Links + +* link:https://github.com/hyperpolymath/betlang[GitHub Repository] +* link:https://betlang.org[Project Homepage] +* link:ui/README.adoc[Quantum Playground Documentation] +* link:playground/README.adoc[Experimental Playground Documentation] +* link:.machine_readable/6a2/ECOSYSTEM.a2ml[Ecosystem Position] +* link:0-AI-MANIFEST.a2ml[AI Agent Manifest] diff --git a/containers/Containerfile.chainguard b/containers/Containerfile.chainguard new file mode 100644 index 0000000..20b81ad --- /dev/null +++ b/containers/Containerfile.chainguard @@ -0,0 +1,105 @@ +# SPDX-License-Identifier: PMPL-1.0-or-later +# Betlang Playground - Chainguard-based Container Image +# +# Uses Chainguard images for minimal, distroless deployment +# Build with: podman build -t betlang-playground -f containers/Containerfile.chainguard . +# +# Why Chainguard? +# - Minimal attack surface (only essential packages) +# - Distroless final image (no shell, no package manager) +# - SBOM-enabled (full Software Bill of Materials) +# - Signed images (cosign signatures for supply chain security) +# - Wolfi-based (Alpine-derived, musl libc) + +# ============================================================================ +# Stage 1: Build UI with Chainguard Deno +# ============================================================================ + +# Chainguard Deno image - minimal Deno runtime +# Contains: Deno, minimal CA certificates, wolfi base +FROM cgr.dev/chainguard/deno:latest AS builder + +# Create non-root user (Chainguard images are already non-root by default) +# The chainguard/deno user is "deno" with UID 1000 +WORKDIR /app + +# Install dependencies for building ReScript +# Chainguard images have minimal packages, so we need to add what's needed +# Note: Chainguard images don't have package managers, so we use the dev variant +# which has apk for installing build dependencies + +# Copy lock files first for caching +COPY ui/deno.json ui/rescript.json ./ + +# Install rescript compiler via deno +RUN deno install -A --root -f https://deno.land/x/rescript/rescript.ts + +# Copy UI source +COPY ui/ ./ui/ + +# Build the ReScript code +RUN cd ui && \ + deno run -A npm:rescript build + +# ============================================================================ +# Stage 2: Runtime Image (Distroless) +# ============================================================================ + +# Chainguard static image - completely empty, just CA certs +# This is the most minimal production image possible +FROM cgr.dev/chainguard/static:latest AS runtime + +# Set up runtime environment +WORKDIR /app + +# Create non-root user (static image has no users by default) +RUN addgroup -S appgroup && adduser -S appuser -G appgroup +USER appuser + +# Copy built assets from builder +COPY --from=builder /app/ui/dist/ ./dist/ +COPY --from=builder /app/ui/public/ ./public/ + +# Copy launcher scripts +COPY ui/launcher/ ./launcher/ + +# Make launcher scripts executable +RUN chmod +x ./launcher/*.sh + +# Set up environment +ENV PORT=3000 +ENV HOST=0.0.0.0 + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:3000/ || exit 1 + +# Default command: start the launcher +ENTRYPOINT ["./launcher/betlang-playground.sh"] +CMD ["--auto"] + +# Labels (comprehensive metadata) +LABEL org.opencontainers.image.title="Betlang Playground" +LABEL org.opencontainers.image.description="interactive playground for Betlang probabilistic programming" +LABEL org.opencontainers.image.source="https://github.com/hyperpolymath/betlang" +LABEL org.opencontainers.image.licenses="PMPL-1.0-or-later" +LABEL org.opencontainers.image.version="1.0.0" +LABEL org.opencontainers.image.revision="$(git rev-parse HEAD 2>/dev/null || echo 'unknown')" +LABEL org.opencontainers.image.created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" +LABEL org.opencontainers.image.vendor="hyperpolymath" +LABEL org.opencontainers.image.authors="Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>" +LABEL maintainer="Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>" + +# SBOM labels (Chainguard generates these automatically, but we add our own) +LABEL org.opencontainers.image.sbom="https://github.com/hyperpolymath/betlang/blob/main/containers/sbom-betlang-playground.spdx.json" + +# Security labels +LABEL org.opencontainers.image.security.scanner="Chainguard Enforce" +LABEL org.opencontainers.image.security.policy="distroless" + +# Launcher standard compliance +LABEL hyperpolymath.launcher.standard="0.2.0" +LABEL hyperpolymath.launcher.modes="--start,--stop,--status,--auto,--browser,--integ,--disinteg,--help,--debug,--logs,--tail" diff --git a/playground/README.adoc b/playground/README.adoc new file mode 100644 index 0000000..9973a4b --- /dev/null +++ b/playground/README.adoc @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: PMPL-1.0-or-later += Betlang Playground Directory +:toc: +:toclevels: 3 +:sectnums: +:source-highlighter: rouge +:icons: font +:description: Playground directory for Betlang - FFI, experimental features, and configuration + +== Overview + +The `playground/` directory serves as a **sandbox and experimental workspace** for Betlang development, separate from the main language implementation in `compiler/` and the web-based UI in `ui/`. + +This directory contains: + +* **FFI bindings** - Foreign Function Interface for integrating Betlang with other languages/runtimes +* **Configuration files** - Project configuration using Nickel, Must, Just +* **Experimental code** - New features under development +* **Documentation** - Architecture and design documents +* **Examples** - Standalone code samples + +== Purpose + +The playground directory is intentionally **decoupled** from the main compiler to allow: + +* Rapid experimentation without affecting core stability +* Testing of new language features +* FFI development across multiple targets (Zig, WASM, etc.) +* Configuration management for the broader project + +== Structure + +[source] +---- +playground/ +├── ARCHITECTURE.adoc # System architecture documentation +├── Justfile # Task runner configuration +├── config.ncl # Nickel-based project configuration +├── deno.json # Deno runtime configuration +├── mustfile.toml # Must framework configuration (RSR compliance) +├── rescript.json # ReScript project configuration +├── docs/ +│ ├── .gitkeep +│ └── CITATIONS.adoc # Academic references +├── examples/ +│ ├── 01_experiment.bet # Experimental Betlang code +│ ├── SafeDOMExample.res # ReScript DOM example +│ └── web-project-deno.json # Deno web project config +├── ffi/ +│ └── zig/ +│ ├── build.zig # Zig build configuration +│ ├── src/main.zig # Zig FFI bindings +│ └── test/integration_test.zig # Integration tests +├── scripts/ +│ └── .gitkeep +├── src/ +│ └── .gitkeep +└── test/ + └── .gitkeep +---- + +== Configuration Files + +=== config.ncl (Nickel Configuration) + +Project metadata and configuration using the Nickel configuration language. + +[source,nickel] +---- +{ + project = { + name = "betlang-playground", + version = "1.0.0", + description = "Playground for Betlang Playground", + license = "PMPL-1.0-or-later", + author = "Jonathan D.A. Jewell", + repository = "https://github.com/hyperpolymath/betlang-playground", + }, + development = { + default_tasks = ["build", "lint", "fmt", "test"], + }, +} +---- + +=== mustfile.toml (Must Framework) + +RSR (Responsible Specification and Release) compliance configuration. + +Features: + +* **Must-have files**: Enforces presence of LICENSE, README, etc. +* **Must-not-have files**: Blocks banned files (Makefile, Dockerfile, etc.) +* **Content requirements**: Validates file contents against patterns +* **Enforcement rules**: Line length, no trailing whitespace, etc. + +=== deno.json (Deno Configuration) + +Deno task runner and TypeScript configuration for playground scripts. + +[source,json] +---- +{ + "tasks": { + "dev": "deno run --watch src/main.ts", + "test": "deno test --allow-read", + "lint": "deno lint", + "fmt": "deno fmt", + "check": "deno check src/**/*.ts", + "probability": "deno run --allow-read src/probability.ts" + } +} +---- + +=== Justfile (Just Task Runner) + +Comprehensive task automation with: + +* Build automation +* Development server +* Testing pipeline +* Linting and formatting +* Documentation building +* Ternary logic demos +* Probabilistic programming examples +* RSR compliance checks + +See `just --list` for all available tasks. + +== FFI (Foreign Function Interface) + +The `ffi/` directory contains bindings for integrating Betlang with other languages. + +=== Zig FFI (ffi/zig/) + +Zig language bindings for Betlang: + +[source,zig] +---- +// build.zig - Build configuration +// src/main.zig - Main FFI entry points +// test/integration_test.zig - Integration tests +---- + +The Zig FFI provides: + +* Low-level memory management +* Type conversions between Betlang and Zig +* Error handling across language boundaries +* Performance-optimized bindings + +=== WASM Integration (Future) + +Planned integration with `compiler/bet-wasm` for: + +* Web-based execution +* Browser-compatible FFI +* JavaScript interoperability +* Edge computing deployment + +== Examples + +=== 01_experiment.bet + +Demonstrates experimental Betlang features: + +[source] +---- +#![feature(linear_types)] +#![feature(dependent_types)] +#![feature(effect_system)] + +@linear +fn example_linear(): + let resource = acquire() + use(resource) + +syntax_variant A: + let x = 5 + let y = x + 10 + +syntax_variant B: + x := 5 + y := x + 10 +---- + +=== SafeDOMExample.res + +ReScript example demonstrating formally verified DOM mounting: + +* Type-safe DOM manipulation +* Error handling with proofs +* Batch mounting operations +* TEA (The Elm Architecture) integration + +=== web-project-deno.json + +Example Deno configuration for ReScript web projects: + +* Deno task definitions +* ReScript dependency management +* Safe DOM library integration + +== Shareable URLs & Ecosystem Integration + +=== URL Encoding for Playground Examples + +Individual examples can be encoded and shared via URL: + +[source] +---- +# Example: Sharing a specific bet expression +https://betlang.org/playground?example=01_experiment + +# Example: Sharing with custom code +https://betlang.org/playground#code= +---- + +=== QR Code Generation + +For physical sharing (workshops, presentations, print): + +[source,bash] +---- +# Generate QR code from URL (requires qrencode) +qrencode -t ANSIUTF8 "https://betlang.org/playground#code=$(echo 'bet {1,2,3}' | base64)" +---- + +=== Social Media Cards + +OpenGraph and Twitter Card metadata for social sharing: + +[source,html] +---- + + + + +---- + +== Integration with 389, AD, K9 Systems + +=== K9 Contractiles + +K9 (Kennel-Yard-Hunt) self-validating contractiles provide: + +* **Kennel Level**: Pure data configuration (no execution) +* **Yard Level**: Validated configuration with Nickel contracts +* **Hunt Level**: Full execution with Just recipes (requires signature) + +The playground integrates with K9 for: + +[source] +---- +.machine_readable/svc/k9/ +├── examples/ +│ ├── project-metadata.k9.ncl +│ ├── ci-config.k9.ncl +│ └── deployment.k9.ncl +└── README.adoc +---- + +=== ADR (Architecture Decision Records) + +All architectural decisions are documented as ADRs: + +* ADR-001: K9 service component organization +* ADR-002: RSR compliance enforcement +* ADR-003: Machine-readable metadata first + +ADR files are stored in `.machine_readable/svc/` and referenced in code. + +=== 389 System Integration + +The "389" refers to the three-level trust model in K9: + +| Level | Trust | Execution | Use Case | +|-------|-------|-----------|----------| +| Kennel | High | None | Configuration, schemas | +| Yard | Medium | Nickel eval | Validated configs | +| Hunt | Low | Full | Deployment, execution | + +Playground components map to these levels: + +* `config.ncl` → Yard (validated configuration) +* `mustfile.toml` → Yard (contract enforcement) +* `Justfile` → Hunt (task execution) + +== Ecosystem Integration + +=== GitHub Integration + +* GitHub Actions for CI/CD +* Issue templates for feature requests +* Pull request templates +* Security policy enforcement + +=== GitLab Compatibility + +The playground maintains compatibility with GitLab: + +* CI/CD pipeline files +* Merge request templates +* Security scanning + +=== IPFS / Decentralized + +For decentralized sharing: + +[source,bash] +---- +# Add playground to IPFS +ipfs add -r playground/ + +# Share via IPFS gateway +ipfs name publish +# Result: /ipns/ +---- + +=== Matrix / Element + +Share via Matrix: + +[source] +---- +https://matrix.to/#/#betlang:matrix.org?web-instance[element.io]=https://betlang.org/playground +---- + +=== Discord + +Rich embed sharing: + +[source] +---- +https://discord.com/app?url=https://betlang.org/playground +---- + +=== Bluesky + +[source] +---- +https://bsky.app/intent/compose?text=Check%20out%20Betlang%20Playground%20https://betlang.org/playground +---- + +== Machine-Readable Metadata + +The playground integrates with the machine-readable metadata system: + +[source] +---- +.machine_readable/ +├── 6a2/ +│ ├── AGENTIC.a2ml # AI agent constraints +│ ├── ECOSYSTEM.a2ml # Project ecosystem position +│ ├── META.a2ml # Project metadata +│ ├── NEUROSYM.a2ml # Neural symbolic integration +│ ├── PLAYBOOK.a2ml # Operational playbook +│ └── STATE.a2ml # Current project state +└── svc/ + └── k9/ + └── README.adoc # K9 contractiles +---- + +Each `.a2ml` file provides machine-readable configuration for: + +* AI agent interactions +* Project positioning in the ecosystem +* Operational procedures +* State tracking + +== Development Workflow + +=== Adding New Features + +1. Create feature branch from `main` +2. Add code to appropriate `playground/` subdirectory +3. Update `ARCHITECTURE.adoc` with design decisions +4. Add tests in `test/` directory +5. Update `mustfile.toml` with new requirements +6. Run `just check` to verify RSR compliance + +=== FFI Development + +For new FFI bindings: + +1. Create language-specific directory under `ffi/` +2. Add build configuration (`build.zig`, `CMakeLists.txt`, etc.) +3. Implement bindings in `src/` +4. Add integration tests in `test/` +5. Document in `ffi//README.adoc` + +=== Configuration Changes + +For configuration updates: + +1. Modify `config.ncl` with new settings +2. Update `mustfile.toml` with new requirements +3. Add validation in `Justfile` +4. Test with `just validate` + +== RSR Compliance + +The playground enforces RSR (Responsible Specification and Release) standards: + +=== Bronze Level (Current) + +* SPDX license headers in all files +* README.adoc present +* LICENSE file present +* .gitignore configured +* Basic project structure + +=== Silver Level (Target) + +* Complete documentation +* Test coverage ≥ 80% +* CI/CD pipeline +* Security policy +* Code of conduct + +=== Gold Level (Future) + +* Formal specifications +* Complete test suite +* Security audits +* Accessibility compliance +* Internationalization + +== License + +This project is licensed under PMPL-1.0-or-later (Palimpsest Metadata Public License). + +All files must include the SPDX license header: + +[source] +---- +// SPDX-License-Identifier: PMPL-1.0-or-later +---- + +== See Also + +* link:../README.adoc[Main Betlang README] +* link:../ui/README.adoc[Quantum Playground README] +* link:../0-AI-MANIFEST.a2ml[AI Agent Manifest] +* link:.machine_readable/6a2/ECOSYSTEM.a2ml[Ecosystem Position] +* link:https://github.com/hyperpolymath/betlang[GitHub Repository] diff --git a/ui/INSTALL.sh b/ui/INSTALL.sh new file mode 100755 index 0000000..f921091 --- /dev/null +++ b/ui/INSTALL.sh @@ -0,0 +1,534 @@ +#!/bin/bash +# SPDX-License-Identifier: PMPL-1.0-or-later +# Betlang Playground - Self-Contained Installation Script +# +# This script installs the Betlang Playground as a complete, standalone package +# with all dependencies handled via containerisation (Podman with Chainguard images) +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/hyperpolymath/betlang/main/ui/INSTALL.sh | bash +# OR +# ./INSTALL.sh +# +# Requirements: +# - Podman (preferred) or Docker +# - Bash 4+ +# - sudo (for system-wide install) +# +# The script will: +# 1. Detect platform and requirements +# 2. Install Podman if not present (on Linux) +# 3. Build or pull the Chainguard-based container image +# 4. Install launcher scripts +# 5. Set up desktop integration (optional) +# 6. Verify installation + +set -euo pipefail + +# ============================================================================ +# Configuration +# ============================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +APP_NAME="betlang-playground" +APP_DISPLAY="Betlang Playground" +APP_VERSION="1.0.0" + +# Installation directories +INSTALL_DIR="/opt/${APP_NAME}" +BIN_DIR="/usr/local/bin" +APPS_DIR="$HOME/.local/share/applications" +ICONS_DIR="$HOME/.local/share/icons/hicolor/256x256/apps" +DESKTOP_DIR="$HOME/Desktop" + +# Container settings +IMAGE_NAME="${APP_NAME}" +IMAGE_TAG="${APP_VERSION}" +CONTAINERFILE="${PROJECT_ROOT}/containers/Containerfile.chainguard" + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# ============================================================================ +# Helper Functions +# ============================================================================ + +log() { + local level="${1:-INFO}" + local message="$2" + local color="" + + case "$level" in + ERROR) color="$RED" ;; + WARN) color="$YELLOW" ;; + SUCCESS) color="$GREEN" ;; + INFO) color="$BLUE" ;; + *) color="" ;; + esac + + echo -e "${color}[${level}]${NC} ${message}" +} + +echo_header() { + echo "" + echo "╔════════════════════════════════════════════════════════════════════" + echo "║ $APP_DISPLAY Installation v$APP_VERSION ║" + echo "╚════════════════════════════════════════════════════════════════════" + echo "" +} + +echo_section() { + echo "" + echo "=== $1 ===" + echo "" +} + +die() { + log "ERROR" "$1" + exit 1 +} + +check_command() { + local cmd="$1" + if ! command -v "$cmd" &> /dev/null; then + return 1 + fi + return 0 +} + +# ============================================================================ +# Platform Detection +# ============================================================================ + +detect_platform() { + log "INFO" "Detecting platform..." + + PLATFORM=$(uname -s) + ARCH=$(uname -m) + + log "INFO" "Platform: $PLATFORM" + log "INFO" "Architecture: $ARCH" + + case "$PLATFORM" in + Linux*) PLATFORM="linux" ;; + Darwin*) PLATFORM="macos" ;; + CYGWIN*|MINGW*|MSYS*) PLATFORM="windows" ;; + *) PLATFORM="unknown" ;; + esac + + case "$ARCH" in + x86_64*) ARCH="amd64" ;; + aarch64*) ARCH="arm64" ;; + arm64*) ARCH="arm64" ;; + *) ARCH="unknown" ;; + esac + + if [ "$PLATFORM" = "unknown" ] || [ "$ARCH" = "unknown" ]; then + die "Unsupported platform: $PLATFORM/$ARCH" + fi +} + +# ============================================================================ +# Requirement Checking +# ============================================================================ + +check_requirements() { + echo_section "Checking Requirements" + + local missing=() + + # Check for Podman (preferred) + if ! check_command podman; then + if ! check_command docker; then + log "WARN" "Neither podman nor docker found" + missing+=("podman or docker") + else + CONTAINER_RUNTIME="docker" + log "INFO" "Found docker (will use as fallback)" + fi + else + CONTAINER_RUNTIME="podman" + log "INFO" "Found podman" + fi + + # Check for bash 4+ + if [ "${BASH_VERSINFO[0]}" -lt 4 ] 2>/dev/null; then + missing+=("bash 4+") + else + log "INFO" "Found bash ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}" + fi + + # Check for sudo (for system install) + if ! check_command sudo; then + log "WARN" "sudo not found - will install to user directory only" + else + log "INFO" "Found sudo" + fi + + if [ ${#missing[@]} -gt 0 ]; then + die "Missing requirements: ${missing[*]}" + fi +} + +# ============================================================================ +# Podman Installation +# ============================================================================ + +install_podman() { + echo_section "Installing Podman" + + case "$PLATFORM" in + linux) + log "INFO" "Installing Podman on Linux..." + + # Try different Linux distros + if check_command apt-get; then + # Debian/Ubuntu + sudo apt-get update + sudo apt-get install -y podman + elif check_command dnf; then + # Fedora/RHEL + sudo dnf install -y podman + elif check_command yum; then + # CentOS/RHEL + sudo yum install -y podman + elif check_command apk; then + # Alpine + sudo apk add --no-cache podman + elif check_command zypper; then + # openSUSE + sudo zypper install -y podman + else + die "Cannot install podman - unsupported package manager" + fi + ;; + macos) + log "INFO" "Installing Podman on macOS..." + log "INFO" "Using Homebrew (may take a while)..." + + if ! check_command brew; then + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + + brew install podman podman-desktop + ;; + *) + die "Cannot auto-install podman on $PLATFORM" + ;; + esac + + # Verify installation + if ! check_command podman; then + die "Podman installation failed" + fi + + log "SUCCESS" "Podman installed successfully" + CONTAINER_RUNTIME="podman" +} + +# ============================================================================ +# Build Container Image +# ============================================================================ + +build_container() { + echo_section "Building Container Image" + + log "INFO" "Using container runtime: $CONTAINER_RUNTIME" + log "INFO" "Building from: $CONTAINERFILE" + + # Change to project root + cd "$PROJECT_ROOT" + + # Build the image + if ! $CONTAINER_RUNTIME build \ + -t "$IMAGE_NAME:$IMAGE_TAG" \ + -f "$CONTAINERFILE" \ + . 2>&1 | tee /tmp/build.log; then + + log "ERROR" "Container build failed" + log "INFO" "Build log saved to: /tmp/build.log" + die "Check /tmp/build.log for details" + fi + + log "SUCCESS" "Container image built: $IMAGE_NAME:$IMAGE_TAG" +} + +pull_container() { + echo_section "Pulling Container Image" + + # Try to pull from GitHub Container Registry + log "INFO" "Attempting to pull pre-built image..." + + if $CONTAINER_RUNTIME pull "ghcr.io/hyperpolymath/${IMAGE_NAME}:${IMAGE_TAG}" 2>&1 | grep -q "Downloaded"; then + log "SUCCESS" "Pulled pre-built image" + return 0 + else + log "INFO" "Pre-built image not available, will build locally" + return 1 + fi +} + +# ============================================================================ +# Install Launcher Scripts +# ============================================================================ + +install_launcher() { + echo_section "Installing Launcher Scripts" + + log "INFO" "Creating installation directory: $INSTALL_DIR" + + # Use sudo if available and INSTALL_DIR requires it + if [ -w "$(dirname "$INSTALL_DIR")" ]; then + SUDO="" + else + SUDO="sudo" + fi + + $SUDO mkdir -p "$INSTALL_DIR/launcher" "$INSTALL_DIR/ui" + + # Copy launcher scripts + log "INFO" "Copying launcher scripts..." + $SUDO cp -r "$SCRIPT_DIR/launcher/"* "$INSTALL_DIR/launcher/" + $SUDO chmod +x "$INSTALL_DIR/launcher/"*.sh + + # Copy UI files (for source-based installation) + log "INFO" "Copying UI files..." + $SUDO cp -r "$SCRIPT_DIR/public/"* "$INSTALL_DIR/ui/public/" + $SUDO cp -r "$SCRIPT_DIR/src/"* "$INSTALL_DIR/ui/src/" + $SUDO cp "$SCRIPT_DIR/deno.json" "$SCRIPT_DIR/.json" "$SCRIPT_DIR/vite.config.js" "$INSTALL_DIR/ui/" + + # Create symlink in PATH + log "INFO" "Creating symlink: $BIN_DIR/$APP_NAME -> $INSTALL_DIR/launcher/$APP_NAME.sh" + $SUDO mkdir -p "$BIN_DIR" + $SUDO ln -sf "$INSTALL_DIR/launcher/$APP_NAME.sh" "$BIN_DIR/$APP_NAME" + + log "SUCCESS" "Launcher installed to $INSTALL_DIR" +} + +# ============================================================================ +# Desktop Integration +# ============================================================================ + +install_desktop() { + echo_section "Installing Desktop Integration" + + # Create directories + mkdir -p "$APPS_DIR" "$ICONS_DIR" "$DESKTOP_DIR" + + # Create icon (placeholder - in production, use actual icon) + log "INFO" "Creating icon..." + cat > "$ICONS_DIR/$APP_NAME.png" <<'EOF' + + + + +B + +EOF + chmod 644 "$ICONS_DIR/$APP_NAME.png" + + # Create desktop file + log "INFO" "Creating desktop file..." + cat > "$APPS_DIR/$APP_NAME.desktop" </dev/null || true + fi + + log "SUCCESS" "Desktop integration installed" +} + +# ============================================================================ +# Verification +# ============================================================================ + +verify_installation() { + echo_section "Verifying Installation" + + local errors=() + + # Check launcher script + if [ ! -x "$BIN_DIR/$APP_NAME" ] && [ ! -x "$INSTALL_DIR/launcher/$APP_NAME.sh" ]; then + errors+=("launcher script not found") + else + log "INFO" "✓ Launcher script installed" + fi + + # Check container image + if ! $CONTAINER_RUNTIME image exists "$IMAGE_NAME:$IMAGE_TAG" &> /dev/null; then + errors+=("container image not found") + else + log "INFO" "✓ Container image available" + fi + + # Check desktop integration + if [ -f "$APPS_DIR/$APP_NAME.desktop" ]; then + log "INFO" "✓ Desktop file installed" + fi + + # Check bin symlink + if [ -L "$BIN_DIR/$APP_NAME" ]; then + log "INFO" "✓ Binary symlink created" + fi + + if [ ${#errors[@]} -gt 0 ]; then + log "WARN" "Verification issues: ${errors[*]}" + else + log "SUCCESS" "All verification checks passed!" + fi +} + +# ============================================================================ +# Main Installation +# ============================================================================ + +main() { + echo_header + + # Step 1: Detect platform + detect_platform + + # Step 2: Check requirements + check_requirements + + # Step 3: Install Podman if needed + if ! check_command podman && ! check_command docker; then + read -p "Podman/Docker not found. Install Podman? [Y/n] " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then + install_podman + else + die "Container runtime required. Please install podman or docker manually." + fi + fi + + # Step 4: Try to pull pre-built image + if ! pull_container; then + # Step 5: Build from source + build_container + fi + + # Step 6: Install launcher + install_launcher + + # Step 7: Desktop integration (optional) + read -p "Install desktop integration? [Y/n] " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then + install_desktop + fi + + # Step 8: Verify + verify_installation + + # Final message + echo "" + log "SUCCESS" "========================================" + log "SUCCESS" "Installation Complete!" + log "SUCCESS" "========================================" + echo "" + log "INFO" "To start the playground, run:" + log "INFO" " $APP_NAME --auto" + echo "" + log "INFO" "Or use the desktop shortcut if you installed desktop integration." + echo "" + log "INFO" "For more information:" + log "INFO" " https://github.com/hyperpolymath/betlang" + echo "" +} + +# ============================================================================ +# Main Entry Point +# ============================================================================ + +# Check if we're being sourced +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + echo "This script must be executed, not sourced" >&2 + exit 1 +fi + +# Parse arguments +INSTALL_ONLY=false +UNINSTALL=false + +while [ $# -gt 0 ]; do + case "$1" in + --install-only) INSTALL_ONLY=true ;; + --uninstall) UNINSTALL=true ;; + --help|-h) usage; exit 0 ;; + *) die "Unknown argument: $1" ;; + esac + shift +done + +if [ "$UNINSTALL" = true ]; then + echo_header + echo_section "Uninstalling $APP_DISPLAY" + + # Remove bin symlink + if [ -L "$BIN_DIR/$APP_NAME" ]; then + log "INFO" "Removing symlink: $BIN_DIR/$APP_NAME" + sudo rm -f "$BIN_DIR/$APP_NAME" + fi + + # Remove installation directory + if [ -d "$INSTALL_DIR" ]; then + log "INFO" "Removing installation directory: $INSTALL_DIR" + sudo rm -rf "$INSTALL_DIR" + fi + + # Remove desktop integration + if [ -f "$APPS_DIR/$APP_NAME.desktop" ]; then + log "INFO" "Removing desktop file" + rm -f "$APPS_DIR/$APP_NAME.desktop" + fi + if [ -f "$DESKTOP_DIR/$APP_NAME.desktop" ]; then + log "INFO" "Removing desktop shortcut" + rm -f "$DESKTOP_DIR/$APP_NAME.desktop" + fi + if [ -f "$ICONS_DIR/$APP_NAME.png" ]; then + log "INFO" "Removing icon" + rm -f "$ICONS_DIR/$APP_NAME.png" + fi + if [ -f "$BIN_DIR/$APP_NAME" ]; then + log "INFO" "Removing bin entry" + sudo rm -f "$BIN_DIR/$APP_NAME" + fi + + # Refresh desktop database + if check_command update-desktop-database; then + update-desktop-database "$APPS_DIR" 2>/dev/null || true + fi + + log "SUCCESS" "Uninstallation complete" + exit 0 +fi + +# Run main installation +main diff --git a/ui/README.adoc b/ui/README.adoc new file mode 100644 index 0000000..491f8bc --- /dev/null +++ b/ui/README.adoc @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: PMPL-1.0-or-later += Betlang Playground +:toc: +:toclevels: 3 +:sectnums: +:source-highlighter: rouge +:icons: font +:description: Interactive web playground for Betlang - ternary probabilistic programming with visualizations + +== Overview + +The Betlang Playground is a **web-based interactive environment** for exploring Betlang's ternary probabilistic programming model through analogies and visualizations. + +Unlike traditional quantum programming playgrounds, this playground demonstrates **probabilistic computation using ternary logic** — where each bet can resolve to one of three (or more) states. + +== Quick Start + +=== Using a Simple Server + +[source,bash] +---- +# Navigate to the ui/public directory +cd ui/public + +# Start a simple Python server (or use any static file server) +python3 -m http.server 3000 + +# Or use PHP +php -S localhost:3000 + +# Or use Deno +deno serve --port 3000 +---- + +Then open http://localhost:3000 in your browser. + +=== Using the Launcher + +[source,bash] +---- +# Use the standards-compliant launcher +./launcher/betlang-playground.sh --auto +---- + +This starts a development server and opens the playground in your browser. + +== Features + +=== Analog Examples + +The playground includes 8 pre-loaded examples covering: + +| Concept | Betlang Feature | Example | +|---------|-----------------|---------| +| Basic choice | Ternary bet with equal weights | `bet { "A", "B", "C" }` | +| Weighted choice | Bet with custom probabilities | `bet/weighted { "high" @ 0.7, "medium" @ 0.2, "low" @ 0.1 }` | +| Hierarchical probability | Nested bets | `bet { "group1", "group2", bet { "a", "b" } }` | +| Pattern matching | Match on bet result | `match result { "A" -> ..., "B" -> ... }` | +| Monte Carlo | Probabilistic sampling | Estimate probability over N trials | +| Parallel trials | Multiple independent bets | Run several bets and aggregate | +| Probability estimation | Distribution counting | Count occurrences over samples | +| Complex hierarchy | Multi-level nested bets | Chain probabilistic choices | + +=== Visualizations + +* **Histogram**: Shows probability distribution from sampled runs +* **Bet Tree**: Visual representation of nested bet structures + +=== Sharing + +* **URL Sharing**: Code is encoded in the URL hash for easy sharing +* **QR Code**: Generate a QR code for mobile access +* **Social Media**: Share to Twitter, Mastodon, Reddit, LinkedIn, Facebook, Bluesky, or Email + +== Architecture + +=== Technology Stack + +| Component | Technology | Purpose | +|-----------|------------|---------| +| Frontend | Plain JavaScript (ES6+) | No build step required | +| Styling | Custom CSS | Dark/light theme ready | +| Launcher | Bash + Deno | Standards-compliant entry point | +| Container | Podman + Chainguard | Secure deployment | + +=== Directory Structure + +[source] +---- +ui/ +├── public/ +│ ├── index.html # Main HTML page +│ ├── app.js # Main application logic +│ └── styles.css # All styles +└── launcher/ + ├── betlang-playground.sh # Primary launcher + ├── keepopen.sh # Fallback ladder wrapper + └── README.adoc # Launcher documentation +---- + +== Usage + +=== Editor + +* Write Betlang-like code in the editor +* Use the example selector to load pre-built examples +* Press **Run** (or Ctrl+Enter) to execute +* Adjust sample count to change the number of runs + +=== Code Syntax + +The playground accepts a simplified Betlang-like syntax: + +[source] +---- +// Basic bet - equal probability +let outcome = bet { "A", "B", "C" } + +// Weighted bet - custom probabilities +let weighted = bet/weighted { + "high" @ 0.7, + "medium" @ 0.2, + "low" @ 0.1 +} + +// Nested bets +let nested = bet { "x", "y", bet { "a", "b" } } + +// Pattern matching +match result { + "A" -> "First", + "B" -> "Second", + _ -> "Other" +} + +// Loops +for i in 1..100 { + let r = bet { "hit", "miss" } +} +---- + +== Educational Focus + +The Betlang Playground emphasizes four key areas: + +=== 1. Conceptual Foundations + +* **Ternary Logic**: Beyond binary true/false - three states: True, False, Unknown +* **Probabilistic Choice**: Non-deterministic selection with defined probabilities +* **Hierarchical Probability**: Nested probability structures + +=== 2. Computer Science + +* **Pattern Matching**: Clean, expressive control flow +* **Functional Programming**: Immutable data, pure functions +* **Type Safety**: Strong typing prevents many errors + +=== 3. Mathematics + +* **Probability Distributions**: Visualize outcome frequencies +* **Monte Carlo Methods**: Estimate values through random sampling +* **Weighted Sampling**: Non-uniform probability distributions + +=== 4. Methods + +* **Hierarchical Modeling**: Build complex systems from simple bets +* **Nested Probability**: Chain probabilistic decisions +* **Estimation Techniques**: Derive probabilities from samples + +== Integration + +=== Desktop Integration + +Use the launcher's `--integ` mode to install desktop shortcuts: + +[source,bash] +---- +./launcher/betlang-playground.sh --integ +---- + +This creates: +* Desktop file in `~/.local/share/applications/` +* Desktop shortcut +* Application menu entry +* Binary symlink in `~/.local/bin/` + +=== Containerisation + +Use the Chainguard-based container image: + +[source,bash] +---- +podman build -t betlang-playground -f containers/Containerfile.chainguard . +podman run -it --rm -p 3000:3000 betlang-playground +---- + +== Standards Compliance + +=== Launcher Standard v0.2.0 + +All required modes are implemented: + +| Mode | Description | Status | +|------|-------------|--------| +| `--start` | Start in background | ✅ | +| `--stop` | Stop running instance | ✅ | +| `--status` | Check running status | ✅ | +| `--auto` | Auto-detect and start | ✅ | +| `--browser` | Open in browser | ✅ | +| `--integ` | Install desktop integration | ✅ | +| `--disinteg` | Remove desktop integration | ✅ | +| `--help` | Show help | ✅ | + +=== Fallback Ladder (389 System / K9 Trust Model) + +[source] +---- +Stage 1: GUI (yellow banner) + ↓ on failure +Stage 2: TUI (red banner) + ↓ on failure +Stage 3: Shell (green banner) → Interactive bash +---- + +The `keepopen.sh` wrapper ensures all failures are visible through: +1. Loud console banners +2. GUI dialogs (kdialog, zenity, notify-send, xmessage) +3. Always-to-stderr logging + +== Development + +=== Project Structure + +[source] +---- +betlang/ +├── ui/ # Web playground +│ ├── public/ +│ │ ├── index.html # HTML entry point +│ │ ├── app.js # JavaScript application +│ │ └── styles.css # CSS styles +│ └── launcher/ +│ ├── betlang-playground.sh +│ └── keepopen.sh +├── containers/ +│ └── Containerfile.chainguard # Chainguard-based image +└── README.adoc # Main project docs +---- + +=== Running Locally + +[source,bash] +---- +# Method 1: Direct file access (no server) +# Just open public/index.html in a browser + +# Method 2: Simple HTTP server +cd ui/public +python3 -m http.server 3000 + +# Method 3: Launcher +./ui/launcher/betlang-playground.sh --auto +---- + +== Testing + +=== Manual Testing + +1. Open the playground in a browser +2. Select each example from the dropdown +3. Click **Run** and verify output +4. Check histogram visualization +5. Test sharing functionality +6. Try loading from URL hash + +=== Browser Compatibility + +| Browser | Support | Notes | +|---------|---------|-------| +| Chrome/Edge | ✅ Full | Recommended | +| Firefox | ✅ Full | Works well | +| Safari | ✅ Full | Tested | +| Mobile browsers | ✅ Most | QR code useful here | + +== Future Enhancements + +* [ ] **Actual Betlang WASM backend** for real execution +* [ ] **Proper QR code library** instead of placeholder +* [ ] **Syntax highlighting** for Betlang code +* [ ] **Local storage** to save code between sessions +* [ ] **Tutorial mode** with guided lessons +* [ ] **Export/import** code snippets +* [ ] **Dark/light theme toggle** +* [ ] **Accessibility improvements** + +== License + +All files are licensed under PMPL-1.0-or-later (Polyform Noncommercial License 1.0). + +== See Also + +* link:../../README.adoc[Main Betlang README] +* https://github.com/hyperpolymath/betlang[Betlang on GitHub] +* https://github.com/hyperpolymath/standards[Hyperpolymath Standards] diff --git a/ui/deno.json b/ui/deno.json deleted file mode 100644 index 60fb0de..0000000 --- a/ui/deno.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@betlang/ui", - "version": "0.1.0", - "exports": "./src/App.res.mjs", - "tasks": { - "dev": "deno run --allow-all npm:vite", - "build": "deno run --allow-all npm:vite build", - "preview": "deno run --allow-all npm:vite preview", - "res:build": "deno run --allow-all npm:rescript build", - "res:watch": "deno run --allow-all npm:rescript build -w", - "res:clean": "deno run --allow-all npm:rescript clean" - }, - "imports": { - "rescript-tea": "npm:rescript-tea@^0.9.0", - "rescript": "^12.0.0", - "vite": "npm:vite@^5.0.0" - }, - "compilerOptions": { - "lib": ["deno.window", "dom"], - "jsx": "react-jsx", - "jsxImportSource": "npm:react" - } -} diff --git a/ui/launcher/README.adoc b/ui/launcher/README.adoc new file mode 100644 index 0000000..bd11813 --- /dev/null +++ b/ui/launcher/README.adoc @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: PMPL-1.0-or-later += Betlang Playground Launcher +:toc: +:toclevels: 3 +:sectnums: +:source-highlighter: rouge +:icons: font + +== Overview + +This directory contains the **standards-compliant launcher** for the Betlang Playground, following the specification at: + +* https://github.com/hyperpolymath/standards/tree/main/launcher/launcher-standard.a2ml +* https://github.com/hyperpolymath/standards/blob/main/launcher/README.adoc + +== Launcher Standard Compliance + +This launcher implements **launcher-standard v0.2.0** and provides: + +=== Required Modes + +| Mode | Description | Status | +|------|-------------|--------| +| `--start` | Start application in background | ✅ Implemented | +| `--stop` | Stop running application | ✅ Implemented | +| `--status` | Check if running | ✅ Implemented | +| `--auto` | Auto-select based on environment | ✅ Implemented | +| `--browser` | Open in browser | ✅ Implemented | + +=== Integration Modes + +| Mode | Description | Status | +|------|-------------|--------| +| `--integ` | Install desktop integration | ✅ Implemented | +| `--disinteg` | Remove desktop integration | ✅ Implemented | + +=== Meta Modes + +| Mode | Description | Status | +|------|-------------|--------| +| `--help` | Show help | ✅ Implemented | + +=== Optional Modes (Developer) + +| Mode | Description | Status | +|------|-------------|--------| +| `--debug` | Start with debug output | ✅ Implemented | +| `--logs` | Show application logs | ✅ Implemented | +| `--tail` | Tail application log file | ✅ Implemented | + +== Fallback Ladder + +The launcher implements the **389 system** (K9 trust model) with a **fallback ladder**: + +[source] +---- +Stage 1: GUI (yellow banner) + ↓ on failure +Stage 2: TUI (red banner) + ↓ on failure +Stage 3: Shell (green banner) → Interactive bash at repo root +---- + +The `keepopen.sh` wrapper ensures that **every failure is visible** through: + +1. **Loud console banners** (intentionally ugly - visibility > aesthetics) +2. **GUI dialogs** (kdialog, zenity, notify-send, xmessage - whichever is available) +3. **Always-also-to-stderr** logging + +== Files + +[source] +---- +launcher/ +├── betlang-playground.sh # Primary launcher entry point +├── keepopen.sh # Fallback ladder wrapper +└── README.adoc # This file +---- + +=== betlang-playground.sh + +The primary launcher script that implements all required modes. + +**Key Features:** + +* Mode dispatch based on command-line arguments +* Process management (start/stop/status) +* Environment detection (GUI vs TUI) +* Browser auto-detection and launching +* Desktop integration (install/uninstall) +* Log management + +=== keepopen.sh + +The fallback ladder wrapper that ensures failures are visible. + +**Calling Convention:** + +[source,bash] +---- +keepopen.sh APP_NAME REPO_DIR "GUI_CMD" "TUI_CMD" [LOG_FILE] +---- + +**Example:** + +[source,bash] +---- +keepopen.sh betlang-playground /opt/betlang/ui \ + "deno task dev" \ + "deno task dev --no-browser" \ + /tmp/betlang-playground.log +---- + +== Usage + +=== Basic Commands + +[source,bash] +---- +# Show help +./launcher/betlang-playground.sh --help + +# Start in background +./launcher/betlang-playground.sh --start + +# Auto-start (default - starts and opens browser in GUI) +./launcher/betlang-playground.sh --auto + +# Check status +./launcher/betlang-playground.sh --status + +# Stop +./launcher/betlang-playground.sh --stop + +# Open browser (if already running) +./launcher/betlang-playground.sh --browser +---- + +=== Desktop Integration + +[source,bash] +---- +# Install desktop shortcuts, icons, and menu entries +./launcher/betlang-playground.sh --integ + +# Remove desktop integration +./launcher/betlang-playground.sh --disinteg +---- + +=== Developer Commands + +[source,bash] +---- +# Start with debug output +./launcher/betlang-playground.sh --debug + +# Show logs +./launcher/betlang-playground.sh --logs + +# Tail logs in real-time +./launcher/betlang-playground.sh --tail +---- + +=== Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `BETLANG_PORT` | Port to use for dev server | 3000 | +| `BETLANG_BROWSER` | Browser command to use | auto-detect | +| `DISPLAY` | X11 display | (system) | +| `WAYLAND_DISPLAY` | Wayland display | (system) | + +== Desktop Integration Details + +=== Installation (`--integ`) + +Creates the following files: + +[source] +---- +~/.local/share/applications/betlang-playground.desktop +~/.local/share/icons/hicolor/256x256/apps/betlang-playground.png +~/Desktop/betlang-playground.desktop +~/.local/bin/betlang-playground +---- + +The desktop file includes: + +* Application name and description +* Exec line pointing to the launcher script +* Icon reference +* Categories (Development, Education, Science, Math) +* MIME type association + +=== Uninstallation (`--disinteg`) + +Removes all installed files: + +* Desktop file from `~/.local/share/applications/` +* Desktop shortcut from `~/Desktop/` +* Icon from `~/.local/share/icons/` +* Binary symlink from `~/.local/bin/` + +== Containerisation (Podman with Chainguard) + +For a **complete standalone package**, use the Chainguard-based container images. + +=== Why Chainguard? + +Chainguard images are: + +* **Minimal** - Only essential packages, tiny attack surface +* **Distroless** - No shell, no package managers in production +* **SBOM-enabled** - Full Software Bill of Materials +* **Signed** - Cosign signatures for supply chain security +* **Wolfi-based** - Alpine-derived, musl libc + +=== Containerfile (Chainguard) + +See `containers/Containerfile.chainguard` for a Chainguard-based build. + +**Key differences from Debian-based:** + +* Uses `cgr.dev/chainguard/deno` instead of `deno:alpine` +* Uses `cgr.dev/chainguard/node` for Node.js dependencies +* Minimal base with only required packages +* Non-root user by default +* Distroless final image + +=== Building with Podman + +[source,bash] +---- +# Build the Chainguard-based image +cd /home/hyperpolymath/dev/betlang +podman build -t betlang-playground -f containers/Containerfile.chainguard . + +# Run it +podman run -it --rm -p 3000:3000 betlang-playground +---- + +=== Stapeln (Podman Pods) + +For a **stapeln-reinforced** setup with multiple containers: + +[source,bash] +---- +# Create a pod for the playground +podman pod create --name betlang-pod -p 3000:3000 + +# Add the playground container +podman run -d --pod betlang-pod --name betlang-ui \ + -v /home/hyperpolymath/dev/betlang/ui:/app \ + cgr.dev/chainguard/deno:latest \ + deno task dev --host 0.0.0.0 --port 3000 + +# Open in browser +xdg-open http://localhost:3000 +---- + +== Single-File Download Package + +For users who want a **complete self-contained package**: + +=== Package Structure + +[source] +---- +betlang-playground-v1.0.0/ +├── README.adoc # Instructions +├── launcher/ +│ ├── betlang-playground.sh # Launcher script +│ └── keepopen.sh # Fallback wrapper +├── ui/ +│ ├── src/App.res # Plain JavaScript source +│ ├── public/ # Static assets +│ │ ├── index.html +│ │ └── styles.css +│ ├── deno.json +│ ├── .json +│ └── vite.config.js +├── Containerfile # For container builds +├── INSTALL.sh # Installation script +└── LICENSE # PMPL-1.0-or-later +---- + +=== Installation Script (INSTALL.sh) + +[source,bash] +---- +#!/bin/bash +# Self-extracting installation script + +# Detect platform +PLATFORM=$(uname -s) +ARCH=$(uname -m) + +# Install to /opt/betlang-playground +INSTALL_DIR="/opt/betlang-playground" + +# Create directories +sudo mkdir -p "$INSTALL_DIR/launcher" "$INSTALL_DIR/ui" + +# Copy files +sudo cp -r launcher/* "$INSTALL_DIR/launcher/" +sudo cp -r ui/* "$INSTALL_DIR/ui/" +sudo cp Containerfile "$INSTALL_DIR/" + +# Make scripts executable +sudo chmod +x "$INSTALL_DIR/launcher/"*.sh + +# Create symlink in PATH +sudo ln -sf "$INSTALL_DIR/launcher/betlang-playground.sh" /usr/local/bin/betlang-playground + +# Install desktop integration +sudo "$INSTALL_DIR/launcher/betlang-playground.sh" --integ + +echo "Installation complete!" +echo "Run: betlang-playground --auto" +---- + +== Complete Package Requirements + +For a **truly complete package** with no external dependencies: + +[source] +---- +Prerequisites (handled by package): +├── Podman (container runtime) +│ ├── Install from: https://podman.io/ +│ └── Or use: nerdctl (containerd-native) +├── Deno (JavaScript/TypeScript runtime) +│ └── Bundled in Chainguard image +└── Browser (for GUI mode) + └── User-provided or system default + +Package provides: +├── All source code +├── All configuration +├── Launcher scripts +├── Container images (optional) +└── Desktop integration (optional) +---- + +== Compliance Checklist + +- [x] SPDX license headers in all files +- [x] Required modes implemented (`--start`, `--stop`, `--status`, `--auto`, `--browser`) +- [x] Integration modes implemented (`--integ`, `--disinteg`) +- [x] Meta mode implemented (`--help`) +- [x] Optional modes implemented (`--debug`, `--logs`, `--tail`) +- [x] Fallback ladder (keepopen.sh) +- [x] Loud banner visibility +- [x] GUI dialog support +- [x] Always-also-to-stderr +- [x] Desktop file creation +- [x] Icon installation +- [x] Menu entry creation + +== Testing + +=== Test Fallback Ladder + +[source,bash] +---- +# Force a failure to test fallback +./launcher/keepopen.sh betlang-test /opt/betlang/ui \ + "false" \ # GUI command that will fail + "false" \ # TUI command that will fail + /tmp/betlang-test.log + +# Expected: Yellow banner → Red banner → Green banner → Shell +---- + +=== Test Desktop Integration + +[source,bash] +---- +# Install +./launcher/betlang-playground.sh --integ + +# Verify +ls ~/.local/share/applications/betlang-playground.desktop +ls ~/.local/bin/betlang-playground + +# Uninstall +./launcher/betlang-playground.sh --disinteg + +# Verify removal +ls ~/.local/share/applications/betlang-playground.desktop 2>&1 || echo "Removed" +---- + +== See Also + +* link:../README.adoc[Betlang UI README] +* link:../../README.adoc[Main Betlang README] +* https://github.com/hyperpolymath/standards/tree/main/launcher/[Launcher Standard] +* https://github.com/hyperpolymath/standards/blob/main/launcher/launcher-standard.a2ml[Launcher Standard (A2ML)] diff --git a/ui/launcher/betlang-playground.sh b/ui/launcher/betlang-playground.sh new file mode 100755 index 0000000..e7af985 --- /dev/null +++ b/ui/launcher/betlang-playground.sh @@ -0,0 +1,400 @@ +#!/bin/bash +# SPDX-License-Identifier: PMPL-1.0-or-later +# Betlang Playground Launcher +# Compliant with: https://github.com/hyperpolymath/standards/tree/main/launcher/launcher-standard.a2ml + +# This script is the primary entry point for the Betlang Playground +# It implements the launcher standard's required modes and fallback ladder + +set -euo pipefail + +# ============================================================================ +# Configuration +# ============================================================================ + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +APP_NAME="betlang-playground" +APP_DISPLAY="Betlang Playground" +APP_URL="https://github.com/hyperpolymath/betlang" +STANDARDS_COMPLIANCE="launcher-standard-0.2.0" + +# Runtime mode configuration +# Use Python's simple HTTP server, falling back to PHP, then Deno +GUI_CMD="python3 -m http.server 3000" +TUI_CMD="python3 -m http.server 3000" +LOG_FILE="/tmp/${APP_NAME}.log" + +# Required modes per launcher-standard.a2ml +MODES=("--start" "--stop" "--status" "--auto" "--browser" "--integ" "--disinteg" "--help" "--debug" "--logs" "--tail") + +# ============================================================================ +# Helper Functions +# ============================================================================ + +log() { + local level="${1:-INFO}" + local message="$2" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $message" >> "$LOG_FILE" 2>/dev/null || true + echo "[$level] $message" >&2 +} + +usage() { + cat < /dev/null 2>&1; then + log "WARN" "Application already appears to be running" + echo "Application is already running (PID: $(pgrep -f 'http.server'))" >&2 + exit 1 + fi + + log "INFO" "Starting $APP_DISPLAY on port $port" + + # Wrap in keepopen.sh for fallback ladder + exec "$SCRIPT_DIR/keepopen.sh" \ + "$APP_NAME" \ + "$PROJECT_ROOT/public" \ + "cd $PROJECT_ROOT/public && $GUI_CMD" \ + "cd $PROJECT_ROOT/public && $TUI_CMD" \ + "/tmp/${APP_NAME}.log" +} + +# ============================================================================ +# Mode: --stop +# ============================================================================ + +mode_stop() { + log "INFO" "Stopping $APP_DISPLAY" + + local pids + pids=$(pgrep -f "http.server\|python3 -m http" 2>/dev/null || true) + + if [ -z "$pids" ]; then + log "INFO" "No running instance found" + echo "No running instance found" >&2 + exit 0 + fi + + log "INFO" "Killing processes: $pids" + kill $pids 2>/dev/null || true + + # Wait for processes to die + local count=0 + while pgrep -f "http.server\|python3 -m http" > /dev/null 2>&1 && [ $count -lt 10 ]; do + sleep 1 + count=$((count + 1)) + done + + if pgrep -f "http.server\|python3 -m http" > /dev/null 2>&1; then + log "WARN" "Processes did not stop gracefully, force killing" + kill -9 $pids 2>/dev/null || true + fi + + log "INFO" "$APP_DISPLAY stopped" + echo "Stopped" >&2 +} + +# ============================================================================ +# Mode: --status +# ============================================================================ + +mode_status() { + if pgrep -f "http.server\|python3 -m http" > /dev/null 2>&1; then + local pid=$(pgrep -f "http.server\|python3 -m http" | head -1) + echo "RUNNING (PID: $pid)" + exit 0 + else + echo "STOPPED" + exit 1 + fi +} + +# ============================================================================ +# Mode: --auto +# ============================================================================ + +mode_auto() { + # Check if already running + if mode_status > /dev/null 2>&1; then + log "INFO" "Already running, opening browser" + mode_browser + exit 0 + fi + + # Start based on environment + if [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then + # GUI environment - try browser + log "INFO" "GUI environment detected, starting with browser" + mode_start & + sleep 3 + mode_browser + else + # TUI environment + log "INFO" "TUI environment detected" + exec $TUI_CMD + fi +} + +# ============================================================================ +# Mode: --browser +# ============================================================================ + +mode_browser() { + local port="${BETLANG_PORT:-3000}" + local url="http://localhost:$port" + local browser="${BETLANG_BROWSER:-}" + + # Try to detect browser + if [ -z "$browser" ]; then + if command -v xdg-open &> /dev/null; then + browser="xdg-open" + elif command -v open &> /dev/null; then + browser="open" + elif command -v start &> /dev/null; then + browser="start" + fi + fi + + if [ -z "$browser" ]; then + log "ERROR" "No browser detected and BETLANG_BROWSER not set" + echo "Cannot open browser. Set BETLANG_BROWSER or install xdg-open." >&2 + exit 1 + fi + + log "INFO" "Opening $url in $browser" + $browser "$url" 2>/dev/null || true +} + +# ============================================================================ +# Mode: --integ (Install) +# ============================================================================ + +mode_integ() { + log "INFO" "Installing desktop integration for $APP_DISPLAY" + + local apps_dir="$HOME/.local/share/applications" + local icons_dir="$HOME/.local/share/icons/hicolor/256x256/apps" + local desktop_shortcut_dir="$HOME/Desktop" + local bin_dir="$HOME/.local/bin" + + # Create directories + mkdir -p "$apps_dir" "$icons_dir" "$desktop_shortcut_dir" "$bin_dir" + + # Copy icon (placeholder - replace with actual icon) + if [ -f "$PROJECT_ROOT/ui/public/favicon.png" ]; then + cp "$PROJECT_ROOT/ui/public/favicon.png" "$icons_dir/$APP_NAME.png" + chmod 644 "$icons_dir/$APP_NAME.png" + else + # Create a placeholder icon + log "WARN" "No icon found, creating placeholder" + fi + + # Create desktop file + local desktop_file="$apps_dir/$APP_NAME.desktop" + cat > "$desktop_file" < /dev/null; then + update-desktop-database "$apps_dir" 2>/dev/null || true + fi + + log "INFO" "Desktop integration installed successfully" + echo "Desktop integration installed:" >&2 + echo " - Desktop file: $desktop_file" >&2 + echo " - Binary: $bin_dir/$APP_NAME" >&2 + echo " - Shortcut: $desktop_shortcut_dir/$APP_NAME.desktop" >&2 +} + +# ============================================================================ +# Mode: --disinteg (Uninstall) +# ============================================================================ + +mode_disinteg() { + log "INFO" "Removing desktop integration for $APP_DISPLAY" + + local apps_dir="$HOME/.local/share/applications" + local icons_dir="$HOME/.local/share/icons/hicolor/256x256/apps" + local desktop_shortcut_dir="$HOME/Desktop" + local bin_dir="$HOME/.local/bin" + + # Remove desktop file + rm -f "$apps_dir/$APP_NAME.desktop" + + # Remove desktop shortcut + rm -f "$desktop_shortcut_dir/$APP_NAME.desktop" + + # Remove icon + rm -f "$icons_dir/$APP_NAME.png" + + # Remove bin symlink + rm -f "$bin_dir/$APP_NAME" + + # Refresh desktop database + if command -v update-desktop-database &> /dev/null; then + update-desktop-database "$apps_dir" 2>/dev/null || true + fi + + log "INFO" "Desktop integration removed successfully" + echo "Desktop integration removed" >&2 +} + +# ============================================================================ +# Mode: --debug +# ============================================================================ + +mode_debug() { + log "INFO" "Starting in debug mode" + echo "Debug mode enabled" >&2 + echo "Log file: $LOG_FILE" >&2 + echo "" >&2 + + # Start with verbose output + export RUST_BACKTRACE=1 + export DENO_LOG=debug + + exec $GUI_CMD +} + +# ============================================================================ +# Mode: --logs +# ============================================================================ + +mode_logs() { + if [ -f "$LOG_FILE" ]; then + echo "=== $APP_DISPLAY Logs ===" >&2 + echo "File: $LOG_FILE" >&2 + echo "" >&2 + tail -n 100 "$LOG_FILE" 2>/dev/null || true + else + log "INFO" "No log file found: $LOG_FILE" + echo "No log file found: $LOG_FILE" >&2 + exit 1 + fi +} + +# ============================================================================ +# Mode: --tail +# ============================================================================ + +mode_tail() { + if [ -f "$LOG_FILE" ]; then + exec tail -f "$LOG_FILE" + else + log "INFO" "Waiting for log file: $LOG_FILE" + echo "Waiting for log file: $LOG_FILE" >&2 + while [ ! -f "$LOG_FILE" ]; do + sleep 1 + done + exec tail -f "$LOG_FILE" + fi +} + +# ============================================================================ +# Main +# ============================================================================ + +# Check if called with a mode +if [ $# -gt 0 ]; then + MODE="$1" + shift + + case "$MODE" in + --start) mode_start "$@" ;; + --stop) mode_stop "$@" ;; + --status) mode_status "$@" ;; + --auto) mode_auto "$@" ;; + --browser) mode_browser "$@" ;; + --integ) mode_integ "$@" ;; + --disinteg) mode_disinteg "$@" ;; + --help) mode_help "$@" ;; + --debug) mode_debug "$@" ;; + --logs) mode_logs "$@" ;; + --tail) mode_tail "$@" ;; + *) + echo "Unknown mode: $MODE" >&2 + usage + exit 1 + ;; + esac +else + # Default mode: --auto + mode_auto +fi diff --git a/ui/launcher/keepopen.sh b/ui/launcher/keepopen.sh new file mode 100755 index 0000000..f08f7c9 --- /dev/null +++ b/ui/launcher/keepopen.sh @@ -0,0 +1,178 @@ +#!/bin/bash +# SPDX-License-Identifier: PMPL-1.0-or-later +# keepopen.sh - Fallback ladder wrapper for Betlang Playground +# Compliant with: https://github.com/hyperpolymath/standards/tree/main/launcher/launcher-standard.a2ml + +# Usage: keepopen.sh APP_NAME REPO_DIR "GUI_CMD" "TUI_CMD" [LOG_FILE] +# +# Fallback ladder: +# 1. GUI stage (yellow banner on failure) - show banner then try TUI +# 2. TUI stage (red banner on failure) - show banner then drop to shell +# 3. Shell stage (green) - exec bash login at repo dir +# +# Banner visibility is intentionally loud (ugly) - visibility beats aesthetics + +set -euo pipefail + +# ============================================================================ +# Configuration +# ============================================================================ + +APP_NAME="${1:-betlang-playground}" +REPO_DIR="${2:-"(unknown)"}" +GUI_CMD="${3:-"echo GUI mode not configured"}" +TUI_CMD="${4:-"echo TUI mode not configured"}" +LOG_FILE="${5:-/tmp/${APP_NAME}.log}" + +# Banner colors per stage +GUI_COLOR="yellow" +TUI_COLOR="red" +SHELL_COLOR="green" + +# GUI dialog tools to try (in order of preference) +GUI_DIALOG_TOOLS=("kdialog" "zenity" "notify-send" "xmessage") + +# ============================================================================ +# Helper Functions +# ============================================================================ + +log() { + local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1" + echo "$msg" >> "$LOG_FILE" 2>/dev/null || true + echo "$msg" >&2 +} + +show_banner() { + local colour="$1" + local stage="$2" + local message="$3" + local next_attempt="$4" + + log "[FALLBACK] Stage: $stage | Colour: $colour | Message: $message" + + # Always log to stderr + echo "" >&2 + echo "╔════════════════════════════════════════════════════════════════╗" >&2 + echo "║ BETLANG QUANTUM PLAYGROUND - LAUNCHER FAILURE ║" >&2 + echo "╠════════════════════════════════════════════════════════════════╣" >&2 + echo "║ Stage: $stage ║" >&2 + echo "║ Colour: $colour ║" >&2 + echo "║ ║" >&2 + echo "║ Message: $message ║" >&2 + if [ -n "$next_attempt" ]; then + echo "║ ║" >&2 + echo "║ Next attempt: $next_attempt ║" >&2 + fi + echo "║ ║" >&2 + echo "║ Log file: $LOG_FILE ║" >&2 + echo "╚════════════════════════════════════════════════════════════════╝" >&2 + echo "" >&2 + + # Try GUI dialog if available and we're in a GUI context + if [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then + for tool in "${GUI_DIALOG_TOOLS[@]}"; do + if command -v "$tool" &> /dev/null; then + case "$tool" in + kdialog) + KDIALOG_TITLE="Betlang Launcher Failure - $stage" + KDIALOG_MSG="Stage: $stage
Message: $message" + if [ -n "$next_attempt" ]; then + KDIALOG_MSG="$KDIALOG_MSG

Next: $next_attempt" + fi + kdialog --msgbox "$KDIALOG_MSG" --title "$KDIALOG_TITLE" --icon error 2>/dev/null || true + ;; + zenity) + ZENITY_MSG="Stage: $stage\n\nMessage: $message" + if [ -n "$next_attempt" ]; then + ZENITY_MSG="$ZENITY_MSG\n\nNext attempt: $next_attempt" + fi + zenity --error --text="$ZENITY_MSG" --title="Betlang Launcher Failure - $stage" 2>/dev/null || true + ;; + notify-send) + notify-send --urgency=critical "Betlang Launcher Failure" "Stage: $stage - $message" 2>/dev/null || true + ;; + xmessage) + echo "Stage: $stage\n\nMessage: $message\n\nNext: $next_attempt" | xmessage -center -file - 2>/dev/null || true + ;; + esac + break + fi + done + fi +} + +# ============================================================================ +# Stage 1: GUI Mode +# ============================================================================ + +run_gui() { + log "[STAGE 1] Attempting GUI mode: $GUI_CMD" + + if eval "$GUI_CMD" 2>> "$LOG_FILE"; then + log "[STAGE 1] GUI mode succeeded" + exit 0 + else + local exit_code=$? + log "[STAGE 1] GUI mode failed with exit code: $exit_code" + show_banner "$GUI_COLOR" "GUI" "GUI mode failed: $GUI_CMD" "Trying TUI mode..." + fi +} + +# ============================================================================ +# Stage 2: TUI Mode +# ============================================================================ + +run_tui() { + log "[STAGE 2] Attempting TUI mode: $TUI_CMD" + + if eval "$TUI_CMD" 2>> "$LOG_FILE"; then + log "[STAGE 2] TUI mode succeeded" + exit 0 + else + local exit_code=$? + log "[STAGE 2] TUI mode failed with exit code: $exit_code" + show_banner "$TUI_COLOR" "TUI" "TUI mode failed: $TUI_CMD" "Dropping to shell..." + fi +} + +# ============================================================================ +# Stage 3: Shell Mode +# ============================================================================ + +run_shell() { + log "[STAGE 3] Starting fallback shell at: $REPO_DIR" + show_banner "$SHELL_COLOR" "SHELL" "Dropping to interactive shell" "" + + # Change to repo directory if it exists + if [ -d "$REPO_DIR" ]; then + cd "$REPO_DIR" + log "[STAGE 3] Changed to: $(pwd)" + else + log "[STAGE 3] Warning: REPO_DIR not found: $REPO_DIR" + fi + + # Start interactive bash login shell + # Never show "press enter to close" - just drop to shell + exec bash -l +} + +# ============================================================================ +# Main Fallback Ladder +# ============================================================================ + +log "========================================" +log "Starting keepopen.sh for $APP_NAME" +log "Repo directory: $REPO_DIR" +log "Log file: $LOG_FILE" +log "========================================" + +# Ensure log directory exists +mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true + +# Write initial marker +log "Launch attempt started at $(date)" + +# Run the fallback ladder +run_gui +run_tui +run_shell diff --git a/ui/public/app.js b/ui/public/app.js new file mode 100644 index 0000000..7bbc674 --- /dev/null +++ b/ui/public/app.js @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: PMPL-1.0-or-later +// Betlang Playground - Main Application +// Plain JavaScript implementation (no ReScript/AffineScript dependency) + +// ============================================================================ +// Betlang Evaluation (Simulated) +// ============================================================================ + +/** + * Simulate Betlang's bet construct + * In real Betlang: bet { "A", "B", "C" } returns a ternary probabilistic value + * Here we simulate it with weighted random selection + */ +function bet(options, weights = null) { + if (weights === null) { + // Equal probability + const index = Math.floor(Math.random() * options.length); + return options[index]; + } else { + // Weighted probability + const totalWeight = weights.reduce((sum, w) => sum + w, 0); + const r = Math.random() * totalWeight; + let cumulative = 0; + for (let i = 0; i < options.length; i++) { + cumulative += weights[i]; + if (r < cumulative) { + return options[i]; + } + } + return options[options.length - 1]; + } +} + +/** + * Simulate weighted bet: bet/weighted { "high" @ 0.7, "medium" @ 0.2, "low" @ 0.1 } + */ +function weightedBet(obj) { + const options = []; + const weights = []; + for (const [key, value] of Object.entries(obj)) { + if (typeof value === 'object' && value !== null && '@' in value) { + options.push(key); + weights.push(value['@']); + } else { + options.push(key); + weights.push(value); + } + } + return bet(options, weights); +} + +/** + * Parse Betlang-like code and evaluate + * This is a simple parser for demonstration purposes + */ +function evaluateBetlangCode(code) { + const results = []; + const sampleCount = 1000; + + // Simple pattern matching for bet expressions + const betMatch = code.match(/bet\s*\{[^}]*\}/); + const weightedBetMatch = code.match(/bet\/weighted\s*\{[^}]*\}/); + + if (weightedBetMatch) { + const content = weightedBetMatch[0].replace('bet/weighted', '').trim(); + // Parse { "a" @ 0.5, "b" @ 0.5 } + const items = content.slice(1, -1).split(',').map(s => s.trim()); + const options = []; + const weights = []; + for (const item of items) { + const match = item.match(/"([^"]+)"\s*@\s*([\d.]+)/); + if (match) { + options.push(match[1]); + weights.push(parseFloat(match[2])); + } + } + // Run multiple samples + const counts = new Map(); + for (let i = 0; i < sampleCount; i++) { + const result = bet(options, weights); + counts.set(result, (counts.get(result) || 0) + 1); + } + return { type: 'weighted-bet', counts, sampleCount, options }; + } else if (betMatch) { + const content = betMatch[0].replace('bet', '').trim(); + // Parse { "a", "b", "c" } + const options = content.slice(1, -1).split(',').map(s => s.trim().replace(/[""]/g, '')); + // Run multiple samples + const counts = new Map(); + for (let i = 0; i < sampleCount; i++) { + const result = bet(options); + counts.set(result, (counts.get(result) || 0) + 1); + } + return { type: 'bet', counts, sampleCount, options }; + } + + // If no bet found, just return the code as-is + return { type: 'raw', value: code }; +} + +// ============================================================================ +// Examples +// ============================================================================ + +const examples = { + 'Ternary Choice': `// Basic ternary choice +// Each option has equal probability (1/3) + +let outcome = bet { "A", "B", "C" } +outcome`, + + 'Weighted Probability': `// Weighted bet - options have different probabilities +// @ specifies the weight/probability + +let weighted = bet/weighted { + "high" @ 0.7, // 70% chance + "medium" @ 0.2, // 20% chance + "low" @ 0.1 // 10% chance +} +weighted`, + + 'Nested Bets': `// Nested bets (hierarchical probability) +// First bet determines which sub-bet to evaluate + +let nested = bet { + "group1", + "group2", + bet { "a", "b", "c" } +} +nested`, + + 'Parallel Trials': `// Multiple independent bets + +let trial1 = bet { "success", "failure" } +let trial2 = bet { "success", "failure" } +let trial3 = bet { "success", "failure" } + +// Count successes +let successes = [trial1, trial2, trial3].filter(x => x === "success").length +successes`, + + 'Monte Carlo Estimation': `// Monte Carlo estimation of probability +// Run many trials and count outcomes + +let trials = 1000 +let successes = 0 + +for i in 1..trials { + let result = bet { "hit", "miss" } + if result == "hit" { successes = successes + 1 } +} + +let probability = successes / trials +probability`, + + 'Pattern Matching': `// Pattern matching on bet results + +let result = bet { "red", "green", "blue" } + +match result { + "red" -> "Stop" + "green" -> "Go" + "blue" -> "Caution" +}`, + + 'Hierarchical Probability': `// Multi-level probability structure + +let level1 = bet/weighted { + "path_a" @ 0.6, + "path_b" @ 0.4 +} + +match level1 { + "path_a" -> bet { "a1", "a2", "a3" }, + "path_b" -> bet/weighted { "b1" @ 0.5, "b2" @ 0.5 } +}`, + + 'Probability Estimation': `// Estimate probability distribution + +let samples = 5000 +let results = [] + +for i in 1..samples { + let outcome = bet { "option_1", "option_2", "option_3" } + results.push(outcome) +} + +// Count occurrences +let counts = {} +for r in results { + counts[r] = (counts[r] || 0) + 1 +} +counts` +}; + +// ============================================================================ +// UI State +// ============================================================================ + +const state = { + code: examples['Ternary Choice'], + currentExample: 'Ternary Choice', + result: null, + sampleCount: 1000, + running: false +}; + +// ============================================================================ +// DOM Elements +// ============================================================================ + +const elements = { + editor: null, + runBtn: null, + output: null, + visualization: null, + exampleSelector: null, + sampleCount: null, + shareBtn: null, + modal: null +}; + +// ============================================================================ +// Visualization +// ============================================================================ + +function createHistogram(data, sampleCount) { + if (!data || data.type !== 'bet' && data.type !== 'weighted-bet') { + return '

Run code to see visualization

'; + } + + const maxCount = Math.max(...Array.from(data.counts.values())); + const maxBarWidth = 300; + const barHeight = 30; + const spacing = 20; + + let html = ''; + + let y = 10; + for (const [option, count] of data.counts.entries()) { + const percentage = (count / sampleCount * 100).toFixed(1); + const barWidth = (count / maxCount * maxBarWidth); + + html += ``; + html += ``; + html += `${option} (${percentage}%)`; + html += ``; + y += barHeight + spacing; + } + + html += ''; + return html; +} + +function createBetTree(code) { + // Simple tree visualization for nested bets + const hasNested = code.includes('bet {') && code.match(/bet\s*\{[^}]*bet\s*\{[^}]*\}[^}]*\}/); + if (!hasNested) { + return ''; + } + + let html = ''; + html += ''; + html += 'bet'; + html += ''; + html += ''; + html += ''; + html += ''; + html += 'A'; + html += 'B'; + html += ''; + return html; +} + +// ============================================================================ +// Sharing +// ============================================================================ + +function generateShareURL() { + const encodedCode = encodeURIComponent(state.code); + return `${window.location.origin}${window.location.pathname}#code=${encodedCode}`; +} + +function generateQRCode() { + const url = generateShareURL(); + // Simple QR code SVG (placeholder - in production use a proper library) + return ` + + +QR Code +URL: ${encodeURIComponent(generateShareURL())} +(Install QR library) + +`; +} + +function copyToClipboard(text) { + navigator.clipboard.writeText(text).then(() => { + showNotification('Copied to clipboard!'); + }).catch(() => { + // Fallback + const el = document.createElement('textarea'); + el.value = text; + document.body.appendChild(el); + el.select(); + document.execCommand('copy'); + document.body.removeChild(el); + showNotification('Copied to clipboard!'); + }); +} + +function showNotification(message) { + const notification = document.createElement('div'); + notification.className = 'notification'; + notification.textContent = message; + document.body.appendChild(notification); + setTimeout(() => notification.remove(), 3000); +} + +// ============================================================================ +// Social Sharing +// ============================================================================ + +function shareToTwitter() { + const url = generateShareURL(); + const text = encodeURIComponent('Check out this Betlang code: '); + window.open(`https://twitter.com/intent/tweet?text=${text}&url=${url}`, '_blank'); +} + +function shareToMastodon() { + const url = generateShareURL(); + const text = encodeURIComponent('Exploring Betlang probabilistic programming'); + window.open(`https://mastodon.social/share?text=${text} ${url}`, '_blank'); +} + +function shareToReddit() { + const url = generateShareURL(); + const title = encodeURIComponent('Betlang Playground'); + window.open(`https://www.reddit.com/submit?url=${url}&title=${title}`, '_blank'); +} + +function shareToLinkedIn() { + const url = generateShareURL(); + window.open(`https://www.linkedin.com/shareArticle?mini=true&url=${url}`, '_blank'); +} + +function shareToFacebook() { + const url = generateShareURL(); + window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank'); +} + +function shareToBluesky() { + const url = generateShareURL(); + const text = encodeURIComponent('Check out this Betlang code'); + window.open(`https://bsky.app/intent/compose?text=${text} ${url}`, '_blank'); +} + +function shareToEmail() { + const url = generateShareURL(); + window.location.href = `mailto:?subject=Betlang Playground&body=${url}`; +} + +// ============================================================================ +// Load from URL +// ============================================================================ + +function loadFromURL() { + const hash = window.location.hash.substring(1); + const params = new URLSearchParams(hash); + if (params.has('code')) { + const code = decodeURIComponent(params.get('code')); + state.code = code; + // Try to match with an example + for (const [name, exampleCode] of Object.entries(examples)) { + if (exampleCode === code) { + state.currentExample = name; + break; + } + } + } +} + +// ============================================================================ +// Run Code +// ============================================================================ + +function runCode() { + state.running = true; + elements.runBtn.disabled = true; + elements.runBtn.textContent = 'Running...'; + + // Simulate async execution + setTimeout(() => { + try { + const result = evaluateBetlangCode(state.code); + state.result = result; + renderOutput(); + renderVisualization(); + } catch (e) { + elements.output.innerHTML = `
Error: ${e.message}
`; + elements.visualization.innerHTML = ''; + } + state.running = false; + elements.runBtn.disabled = false; + elements.runBtn.textContent = 'Run'; + }, 100); +} + +// ============================================================================ +// Render Functions +// ============================================================================ + +function renderOutput() { + if (!state.result) { + elements.output.innerHTML = '

Run code to see output

'; + return; + } + + if (state.result.type === 'bet' || state.result.type === 'weighted-bet') { + let html = '
Sample Results (' + state.sampleCount + ' runs)
'; + html += ''; + html += ''; + for (const [option, count] of state.result.counts.entries()) { + const percentage = (count / state.sampleCount * 100).toFixed(2); + html += ``; + } + html += '
OptionCountProbability
${option}${count}${percentage}%
'; + elements.output.innerHTML = html; + } else { + elements.output.innerHTML = '
' + JSON.stringify(state.result.value, null, 2) + '
'; + } +} + +function renderVisualization() { + if (!state.result) { + elements.visualization.innerHTML = '

Run code to see visualization

'; + return; + } + + if (state.result.type === 'bet' || state.result.type === 'weighted-bet') { + elements.visualization.innerHTML = createHistogram(state.result, state.sampleCount); + } else { + elements.visualization.innerHTML = createBetTree(state.code); + } +} + +function updateSampleCount() { + state.sampleCount = parseInt(elements.sampleCount.value) || 1000; + if (state.result) { + // Re-run with new sample count + runCode(); + } +} + +function selectExample() { + const selected = elements.exampleSelector.value; + state.code = examples[selected]; + state.currentExample = selected; + elements.editor.value = state.code; + elements.output.innerHTML = '

Run code to see output

'; + elements.visualization.innerHTML = '

Run code to see visualization

'; + state.result = null; + + // Update URL + window.history.pushState({}, '', generateShareURL()); +} + +// ============================================================================ +// Modal +// ============================================================================ + +function showModal() { + elements.modal.style.display = 'block'; + document.getElementById('share-url').value = generateShareURL(); + document.getElementById('qr-code').innerHTML = generateQRCode(); +} + +function hideModal() { + elements.modal.style.display = 'none'; +} + +// ============================================================================ +// Initialize +// ============================================================================ + +function init() { + // Get DOM elements + elements.editor = document.getElementById('editor'); + elements.runBtn = document.getElementById('run-btn'); + elements.output = document.getElementById('output'); + elements.visualization = document.getElementById('visualization'); + elements.exampleSelector = document.getElementById('example-selector'); + elements.sampleCount = document.getElementById('sample-count'); + elements.shareBtn = document.getElementById('share-btn'); + elements.modal = document.getElementById('share-modal'); + + // Set up editor + elements.editor.value = state.code; + + // Populate example selector + for (const name of Object.keys(examples)) { + const option = document.createElement('option'); + option.value = name; + option.textContent = name; + elements.exampleSelector.appendChild(option); + } + elements.exampleSelector.value = state.currentExample; + + // Set sample count + elements.sampleCount.value = state.sampleCount; + + // Load from URL + loadFromURL(); + + // Event listeners + elements.runBtn.addEventListener('click', runCode); + elements.exampleSelector.addEventListener('change', selectExample); + elements.sampleCount.addEventListener('change', updateSampleCount); + elements.shareBtn.addEventListener('click', showModal); + + document.querySelector('.modal-close').addEventListener('click', hideModal); + document.querySelector('.copy-btn').addEventListener('click', () => { + copyToClipboard(generateShareURL()); + }); + + // Social buttons + document.getElementById('share-twitter').addEventListener('click', shareToTwitter); + document.getElementById('share-mastodon').addEventListener('click', shareToMastodon); + document.getElementById('share-reddit').addEventListener('click', shareToReddit); + document.getElementById('share-linkedin').addEventListener('click', shareToLinkedIn); + document.getElementById('share-facebook').addEventListener('click', shareToFacebook); + document.getElementById('share-bluesky').addEventListener('click', shareToBluesky); + document.getElementById('share-email').addEventListener('click', shareToEmail); + + // Close modal on outside click + window.addEventListener('click', (e) => { + if (e.target === elements.modal) { + hideModal(); + } + }); + + // Keyboard shortcuts + window.addEventListener('keydown', (e) => { + if (e.ctrlKey && e.key === 'Enter') { + runCode(); + } + if (e.key === 'Escape') { + hideModal(); + } + }); + + // Initial render + renderOutput(); + renderVisualization(); + + console.log('Betlang Playground initialized'); +} + +// Initialize when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); +} else { + init(); +} diff --git a/ui/public/index.html b/ui/public/index.html index 99623fa..52c5af6 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -1,18 +1,131 @@ - Betlang Playground - - + + + + + + + + + + + + + + + -
- +
+
+ +
+

Betlang Playground

+

Interactive playground for ternary probabilistic programming

+

Like quantumplayground.net, but for Betlang's ternary logic model

+
+
+
+ +
+
+
+ + + +
+ + +
+ +
+
+

Output

+
+
+
+

Visualization

+
+
+
+ +
+

Educational Focus

+

This playground demonstrates Betlang - a language for ternary probabilistic programming.

+
    +
  • Conceptual Foundations: Ternary logic, probabilistic choice, hierarchical probability
  • +
  • Computer Science: Pattern matching, functional programming, type safety
  • +
  • Mathematics: Probability distributions, Monte Carlo methods, weighted sampling
  • +
  • Methods: Hierarchical modeling, nested probability, estimation techniques
  • +
+

Unlike quantum computing (which uses qubits in superposition of 0 and 1), Betlang uses ternary bets that can resolve to one of three states.

+
+
+ + + + + diff --git a/ui/public/styles.css b/ui/public/styles.css index 6f705cb..d859b4a 100644 --- a/ui/public/styles.css +++ b/ui/public/styles.css @@ -1,17 +1,11 @@ -/* SPDX-License-Identifier: MIT OR Apache-2.0 */ +/* SPDX-License-Identifier: PMPL-1.0-or-later */ /* Betlang Playground Styles */ :root { - --font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Consolas, monospace; - --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; -} - -/* Dark theme (default) */ -.app.dark { --bg-primary: #1a1b26; - --bg-secondary: #24283b; - --bg-tertiary: #2f3549; - --text-primary: #c0caf5; + --bg-secondary: #1e1e2e; + --bg-tertiary: #24283b; + --text-primary: #cdd6f4; --text-secondary: #9aa5ce; --accent-primary: #7aa2f7; --accent-secondary: #bb9af7; @@ -19,21 +13,8 @@ --error: #f7768e; --warning: #e0af68; --border: #414868; -} - -/* Light theme */ -.app.light { - --bg-primary: #f5f5f5; - --bg-secondary: #ffffff; - --bg-tertiary: #e8e8e8; - --text-primary: #1a1b26; - --text-secondary: #4a4f6a; - --accent-primary: #2e7de9; - --accent-secondary: #9854f1; - --success: #587539; - --error: #c64343; - --warning: #8a6200; - --border: #c0c8d8; + --font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Consolas, monospace; + --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; } * { @@ -42,155 +23,358 @@ box-sizing: border-box; } -body { +html, body { + height: 100%; font-family: var(--font-sans); + background-color: var(--bg-primary); + color: var(--text-primary); line-height: 1.6; } -.app { - min-height: 100vh; - background: var(--bg-primary); - color: var(--text-primary); - display: flex; - flex-direction: column; -} +/* ============================================================================ + Header + ============================================================================ */ -/* Header */ .header { - background: var(--bg-secondary); + background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%); border-bottom: 1px solid var(--border); padding: 1rem 2rem; + position: sticky; + top: 0; + z-index: 100; +} + +.header-content { + max-width: 1400px; + margin: 0 auto; display: flex; justify-content: space-between; align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.header-links { + display: flex; + gap: 1.5rem; +} + +.header-links a { + color: var(--text-secondary); + text-decoration: none; + font-size: 0.9rem; + transition: color 0.2s; } -.header h1 { - font-size: 1.5rem; - font-weight: 600; +.header-links a:hover { color: var(--accent-primary); } -.theme-toggle { - display: flex; - gap: 0.5rem; +.header-main { + flex: 1; + min-width: 400px; } -.theme-toggle button { - background: var(--bg-tertiary); - border: 1px solid var(--border); - color: var(--text-secondary); - padding: 0.5rem 1rem; - border-radius: 6px; - cursor: pointer; - font-size: 0.875rem; - transition: all 0.2s; +.header-main h1 { + font-size: 1.8rem; + margin-bottom: 0.25rem; + color: var(--text-primary); } -.theme-toggle button:hover { - background: var(--bg-primary); +.header-main .subtitle { + font-size: 1.1rem; + color: var(--text-secondary); + margin-bottom: 0.25rem; } -.theme-toggle button.active { - background: var(--accent-primary); - color: var(--bg-primary); - border-color: var(--accent-primary); +.header-main .tagline { + font-size: 0.9rem; + color: var(--text-secondary); + font-style: italic; } -/* Main content */ +/* ============================================================================ + Main Layout + ============================================================================ */ + .main { - flex: 1; - padding: 1.5rem 2rem; - display: flex; - flex-direction: column; - gap: 1rem; - max-width: 1200px; + max-width: 1400px; margin: 0 auto; - width: 100%; + padding: 2rem; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto; + gap: 2rem; } -/* Examples bar */ -.examples-bar { +/* ============================================================================ + Editor + ============================================================================ */ + +.editor-container { + grid-column: 1; + grid-row: 1; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; +} + +.editor-header { display: flex; + gap: 0.5rem; + padding: 0.75rem 1rem; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border); align-items: center; - gap: 1rem; flex-wrap: wrap; } -.examples-label { +.editor-header select { + flex: 1; + min-width: 200px; + padding: 0.5rem; + background: var(--bg-secondary); + color: var(--text-primary); + border: 1px solid var(--border); + border-radius: 4px; + font-family: var(--font-sans); + font-size: 0.9rem; + cursor: pointer; +} + +.editor-header select:focus { + outline: none; + border-color: var(--accent-primary); +} + +.editor { + width: 100%; + min-height: 400px; + padding: 1rem; + background: transparent; + color: var(--text-primary); + border: none; + outline: none; + resize: none; + font-family: var(--font-mono); + font-size: 0.9rem; + line-height: 1.5; + tab-size: 2; +} + +.editor:focus { + outline: none; +} + +.editor::placeholder { color: var(--text-secondary); - font-weight: 500; } -.examples-list { +.editor-footer { + padding: 0.5rem 1rem; + background: var(--bg-tertiary); + border-top: 1px solid var(--border); display: flex; + justify-content: space-between; + align-items: center; +} + +.sample-count-label { + display: flex; + align-items: center; gap: 0.5rem; - flex-wrap: wrap; + color: var(--text-secondary); + font-size: 0.85rem; } -.example-btn { +.sample-count { + width: 80px; + padding: 0.25rem 0.5rem; background: var(--bg-secondary); + color: var(--text-primary); border: 1px solid var(--border); - color: var(--text-secondary); - padding: 0.375rem 0.75rem; border-radius: 4px; - cursor: pointer; - font-size: 0.8125rem; - transition: all 0.2s; + font-family: var(--font-mono); + font-size: 0.85rem; } -.example-btn:hover { - background: var(--accent-primary); - color: var(--bg-primary); +.sample-count:focus { + outline: none; border-color: var(--accent-primary); } -/* Editor */ -.editor-container { - flex: 1; - min-height: 300px; +/* ============================================================================ + Results + ============================================================================ */ + +.results-container { + grid-column: 1; + grid-row: 2; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; } -.code-editor { - width: 100%; - height: 100%; - min-height: 300px; +.output-panel, +.visualization-panel { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; +} + +.output-panel h3, +.visualization-panel h3 { + font-size: 1rem; + margin-bottom: 1rem; color: var(--text-primary); + border-bottom: 1px solid var(--border); + padding-bottom: 0.5rem; +} + +.output { + min-height: 200px; +} + +.visualization { + min-height: 200px; +} + +.placeholder { + color: var(--text-secondary); + text-align: center; + padding: 2rem; +} + +.result-header { + font-size: 0.85rem; + color: var(--text-secondary); + margin-bottom: 0.5rem; +} + +.result-table { + width: 100%; + border-collapse: collapse; +} + +.result-table th, +.result-table td { + padding: 0.5rem; + text-align: left; + border-bottom: 1px solid var(--border); +} + +.result-table th { + color: var(--text-secondary); + font-size: 0.8rem; + text-transform: uppercase; +} + +.result-table tr:hover { + background: var(--bg-tertiary); +} + +.raw-output { + background: var(--bg-tertiary); + padding: 1rem; + border-radius: 4px; font-family: var(--font-mono); - font-size: 0.9375rem; - line-height: 1.6; - resize: vertical; - outline: none; - transition: border-color 0.2s; + font-size: 0.85rem; + overflow-x: auto; } -.code-editor:focus { - border-color: var(--accent-primary); +.error { + color: var(--error); + padding: 1rem; + background: rgba(247, 118, 142, 0.1); + border-radius: 4px; } -.code-editor::placeholder { +.no-data { color: var(--text-secondary); - opacity: 0.6; + text-align: center; + padding: 2rem; } -/* Toolbar */ -.toolbar { - display: flex; - gap: 0.75rem; +/* ============================================================================ + Histogram Visualization + ============================================================================ */ + +.histogram { + width: 100%; } +.histogram text { + font-family: var(--font-sans); +} + +.bet-tree { + width: 100%; +} + +.bet-tree text { + font-family: var(--font-sans); +} + +/* ============================================================================ + Educational Info + ============================================================================ */ + +.edu-info { + grid-column: 2; + grid-row: 1 / span 2; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.5rem; + max-height: calc(100vh - 200px); + overflow-y: auto; +} + +.edu-info h3 { + font-size: 1.1rem; + margin-bottom: 1rem; + color: var(--text-primary); + border-bottom: 1px solid var(--border); + padding-bottom: 0.5rem; +} + +.edu-info p { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 1rem; + line-height: 1.7; +} + +.edu-list { + list-style: none; + margin-bottom: 1rem; +} + +.edu-list li { + margin-bottom: 0.75rem; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.edu-list strong { + color: var(--accent-primary); +} + +/* ============================================================================ + Buttons + ============================================================================ */ + .btn { - padding: 0.625rem 1.25rem; - border-radius: 6px; - font-size: 0.9375rem; - font-weight: 500; + padding: 0.5rem 1rem; + border: none; + border-radius: 4px; + font-family: var(--font-sans); + font-size: 0.9rem; cursor: pointer; transition: all 0.2s; - border: none; + font-weight: 500; } .btn:disabled { @@ -200,111 +384,216 @@ body { .btn-primary { background: var(--accent-primary); - color: var(--bg-primary); + color: #1a1b26; } .btn-primary:hover:not(:disabled) { - filter: brightness(1.1); + background: #8aaaf7; } .btn-secondary { - background: var(--bg-secondary); - border: 1px solid var(--border); + background: var(--bg-tertiary); color: var(--text-primary); + border: 1px solid var(--border); } .btn-secondary:hover:not(:disabled) { - background: var(--bg-tertiary); + background: var(--border); } -/* Output */ -.output-container { +/* ============================================================================ + Modal + ============================================================================ */ + +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + z-index: 1000; + align-items: center; + justify-content: center; +} + +.modal-content { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; - overflow: hidden; + width: 90%; + max-width: 600px; + max-height: 80vh; + overflow-y: auto; + padding: 2rem; + position: relative; } -.output-container h3 { - background: var(--bg-tertiary); - padding: 0.75rem 1rem; - font-size: 0.875rem; - font-weight: 500; +.modal-close { + position: absolute; + top: 1rem; + right: 1rem; + font-size: 2rem; + cursor: pointer; color: var(--text-secondary); - border-bottom: 1px solid var(--border); + transition: color 0.2s; } -.output { - padding: 1rem; - font-family: var(--font-mono); - font-size: 0.875rem; - min-height: 100px; - max-height: 300px; - overflow-y: auto; +.modal-close:hover { + color: var(--text-primary); } -.output-result { - color: var(--success); - margin-bottom: 0.25rem; +.modal h2 { + margin-bottom: 1.5rem; + color: var(--text-primary); } -.output-error { - color: var(--error); - margin-bottom: 0.25rem; +.share-section { + margin-bottom: 1.5rem; } -.output-info { - color: var(--text-secondary); - margin-bottom: 0.25rem; +.share-section:last-child { + margin-bottom: 0; } -/* Footer */ -.footer { - background: var(--bg-secondary); - border-top: 1px solid var(--border); - padding: 1rem 2rem; - text-align: center; - color: var(--text-secondary); - font-size: 0.875rem; +.share-section h3 { + font-size: 1rem; + margin-bottom: 0.75rem; + color: var(--text-primary); } -.footer a { - color: var(--accent-primary); - text-decoration: none; +.url-container { + display: flex; + gap: 0.5rem; } -.footer a:hover { - text-decoration: underline; +.url-input { + flex: 1; + padding: 0.5rem; + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border); + border-radius: 4px; + font-family: var(--font-mono); + font-size: 0.85rem; +} + +.url-input:focus { + outline: none; + border-color: var(--accent-primary); +} + +.qr-container { + display: flex; + justify-content: center; + padding: 1rem; + background: var(--bg-tertiary); + border-radius: 4px; } -.separator { - color: var(--border); +.qr-code { + background: white; + padding: 0.5rem; + border-radius: 4px; } -/* Scrollbar styling */ -::-webkit-scrollbar { - width: 8px; - height: 8px; +/* ============================================================================ + Social Buttons + ============================================================================ */ + +.social-buttons { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + justify-content: center; } -::-webkit-scrollbar-track { +.social-btn { + width: 48px; + height: 48px; + padding: 0.75rem; background: var(--bg-tertiary); + border: 1px solid var(--border); + border-radius: 8px; + color: var(--text-primary); + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; } -::-webkit-scrollbar-thumb { - background: var(--border); +.social-btn:hover { + background: var(--accent-primary); + color: #1a1b26; + border-color: var(--accent-primary); +} + +.social-btn svg { + width: 24px; + height: 24px; +} + +/* ============================================================================ + Notification + ============================================================================ */ + +.notification { + position: fixed; + bottom: 2rem; + right: 2rem; + background: var(--accent-primary); + color: #1a1b26; + padding: 1rem 1.5rem; border-radius: 4px; + font-weight: 500; + z-index: 2000; + animation: slideIn 0.3s ease-out; } -::-webkit-scrollbar-thumb:hover { - background: var(--text-secondary); +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } } -/* Responsive */ -@media (max-width: 768px) { - .header { +/* ============================================================================ + Responsive + ============================================================================ */ + +@media (max-width: 1024px) { + .main { + grid-template-columns: 1fr; + } + + .results-container { + grid-column: 1; + grid-row: 2; + } + + .edu-info { + grid-column: 1; + grid-row: 3; + } + + .header-content { flex-direction: column; - gap: 1rem; + text-align: center; + } + + .header-links { + justify-content: center; + } +} + +@media (max-width: 640px) { + .header { padding: 1rem; } @@ -312,11 +601,47 @@ body { padding: 1rem; } - .toolbar { + .editor-header { flex-direction: column; + gap: 0.75rem; } - .btn { + .editor-header select { width: 100%; } + + .results-container { + grid-template-columns: 1fr; + } + + .social-buttons { + gap: 0.5rem; + } + + .social-btn { + width: 40px; + height: 40px; + padding: 0.5rem; + } +} + +/* ============================================================================ + Syntax Highlighting (Basic) + ============================================================================ */ + +.editor .token-comment { + color: var(--text-secondary); + font-style: italic; +} + +.editor .token-keyword { + color: var(--accent-secondary); +} + +.editor .token-string { + color: var(--success); +} + +.editor .token-number { + color: var(--warning); } diff --git a/ui/rescript.json b/ui/rescript.json deleted file mode 100644 index 0b76ff3..0000000 --- a/ui/rescript.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "betlang-ui", - "version": "0.1.0", - "sources": { - "dir": "src", - "subdirs": true - }, - "package-specs": { - "module": "es6", - "in-source": true - }, - "suffix": ".res.mjs", - "bs-dependencies": [ - "rescript-tea" - ], - "bsc-flags": [ - "-open Tea" - ], - "warnings": { - "number": "+A-42-48-9-30-4" - } -} diff --git a/ui/src/App.res b/ui/src/App.res deleted file mode 100644 index 4e14966..0000000 --- a/ui/src/App.res +++ /dev/null @@ -1,304 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// Betlang Web UI - Main Application -// Uses rescript-tea (The Elm Architecture) - -open Tea - -// ============================================================================ -// Model -// ============================================================================ - -type ternaryValue = - | True - | False - | Unknown - -type outputLine = - | Result(string) - | Error(string) - | Info(string) - -type model = { - code: string, - output: array, - isRunning: bool, - history: array, - historyIndex: int, - theme: string, -} - -let init = () => { - code: "// Welcome to Betlang!\n// Try: bet { 1, 2, 3 }\n\nlet x = bet { \"heads\", \"tails\", \"edge\" }\nx", - output: [], - isRunning: false, - history: [], - historyIndex: -1, - theme: "dark", -} - -// ============================================================================ -// Messages -// ============================================================================ - -type msg = - | UpdateCode(string) - | RunCode - | ClearOutput - | ClearCode - | HistoryUp - | HistoryDown - | SetTheme(string) - | CodeExecuted(result) - | InsertExample(string) - -// ============================================================================ -// Update -// ============================================================================ - -let examples = [ - ("Ternary Bet", "let choice = bet { \"rock\", \"paper\", \"scissors\" }\nprintln(choice)"), - ("Weighted Bet", "let result = bet { win @ 0.6, lose @ 0.3, draw @ 0.1 }\nresult"), - ("Monte Carlo Pi", `let estimate_pi = fun n -> - let inside = ref 0 in - for i = 1 to n do - let x = uniform 0.0 1.0 in - let y = uniform 0.0 1.0 in - if x*x + y*y <= 1.0 then inside := !inside + 1 - done; - 4.0 *. float(!inside) /. float(n) -in estimate_pi 10000`), - ("Distribution", `let samples = replicate 100 (fun () -> normal 0.0 1.0) -let m = mean samples -let s = std samples -println("Mean: " ++ string(m)) -println("Std: " ++ string(s))`), - ("Ternary Logic", `let a = True -let b = Unknown -let c = a && b // Unknown -println(c)`), -] - -let update = (model: model, msg: msg): (model, Cmd.t) => { - switch msg { - | UpdateCode(code) => ({...model, code: code}, Cmd.none) - - | RunCode => { - let newHistory = Belt.Array.concat(model.history, [model.code]) - ( - { - ...model, - isRunning: true, - history: newHistory, - historyIndex: Belt.Array.length(newHistory), - }, - // In a real app, this would call the WASM runtime - Cmd.msg(CodeExecuted(Ok("=> "))) - ) - } - - | ClearOutput => ({...model, output: []}, Cmd.none) - - | ClearCode => ({...model, code: ""}, Cmd.none) - - | HistoryUp => { - let newIndex = max(0, model.historyIndex - 1) - let code = switch Belt.Array.get(model.history, newIndex) { - | Some(c) => c - | None => model.code - } - ({...model, code: code, historyIndex: newIndex}, Cmd.none) - } - - | HistoryDown => { - let newIndex = min(Belt.Array.length(model.history), model.historyIndex + 1) - let code = switch Belt.Array.get(model.history, newIndex) { - | Some(c) => c - | None => "" - } - ({...model, code: code, historyIndex: newIndex}, Cmd.none) - } - - | SetTheme(theme) => ({...model, theme: theme}, Cmd.none) - - | CodeExecuted(result) => { - let line = switch result { - | Ok(output) => Result(output) - | Error(err) => Error(err) - } - ( - { - ...model, - isRunning: false, - output: Belt.Array.concat(model.output, [line]), - }, - Cmd.none - ) - } - - | InsertExample(code) => ({...model, code: code}, Cmd.none) - } -} - -// ============================================================================ -// View -// ============================================================================ - -let viewOutputLine = (line: outputLine): Vdom.t => { - let (className, text) = switch line { - | Result(s) => ("output-result", s) - | Error(s) => ("output-error", s) - | Info(s) => ("output-info", s) - } - Html.div([Attr.class(className)], [Html.text(text)]) -} - -let viewExampleButton = ((name, code): (string, string)): Vdom.t => { - Html.button( - [ - Attr.class("example-btn"), - Events.onClick(InsertExample(code)), - ], - [Html.text(name)] - ) -} - -let view = (model: model): Vdom.t => { - Html.div( - [Attr.class("app " ++ model.theme)], - [ - // Header - Html.header( - [Attr.class("header")], - [ - Html.h1([], [Html.text("Betlang Playground")]), - Html.div( - [Attr.class("theme-toggle")], - [ - Html.button( - [ - Attr.class(model.theme == "dark" ? "active" : ""), - Events.onClick(SetTheme("dark")), - ], - [Html.text("Dark")] - ), - Html.button( - [ - Attr.class(model.theme == "light" ? "active" : ""), - Events.onClick(SetTheme("light")), - ], - [Html.text("Light")] - ), - ] - ), - ] - ), - - // Main content - Html.main( - [Attr.class("main")], - [ - // Examples bar - Html.div( - [Attr.class("examples-bar")], - [ - Html.span([Attr.class("examples-label")], [Html.text("Examples:")]), - Html.div( - [Attr.class("examples-list")], - Belt.Array.map(examples, viewExampleButton)->Belt.Array.toList, - ), - ] - ), - - // Editor - Html.div( - [Attr.class("editor-container")], - [ - Html.textarea( - [ - Attr.class("code-editor"), - Attr.value(model.code), - Attr.placeholder("Enter betlang code..."), - Events.onInput(s => UpdateCode(s)), - ], - [] - ), - ] - ), - - // Toolbar - Html.div( - [Attr.class("toolbar")], - [ - Html.button( - [ - Attr.class("btn btn-primary"), - Attr.disabled(model.isRunning), - Events.onClick(RunCode), - ], - [Html.text(model.isRunning ? "Running..." : "Run (Ctrl+Enter)")] - ), - Html.button( - [ - Attr.class("btn btn-secondary"), - Events.onClick(ClearOutput), - ], - [Html.text("Clear Output")] - ), - Html.button( - [ - Attr.class("btn btn-secondary"), - Events.onClick(ClearCode), - ], - [Html.text("Clear Code")] - ), - ] - ), - - // Output - Html.div( - [Attr.class("output-container")], - [ - Html.h3([], [Html.text("Output")]), - Html.div( - [Attr.class("output")], - Belt.Array.map(model.output, viewOutputLine)->Belt.Array.toList, - ), - ] - ), - ] - ), - - // Footer - Html.footer( - [Attr.class("footer")], - [ - Html.text("Betlang - A ternary probabilistic programming language"), - Html.span([Attr.class("separator")], [Html.text(" | ")]), - Html.a( - [Attr.href("https://github.com/hyperpolymath/betlang")], - [Html.text("GitHub")] - ), - ] - ), - ] - ) -} - -// ============================================================================ -// Subscriptions -// ============================================================================ - -let subscriptions = (_model: model): Sub.t => { - Sub.none -} - -// ============================================================================ -// Main -// ============================================================================ - -let main = App.standardProgram({ - init: () => (init(), Cmd.none), - update: update, - view: view, - subscriptions: subscriptions, -}) From 1ebca84b76e65b730f77b6d07cdc88ef67882c20 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 16 May 2026 17:42:10 +0100 Subject: [PATCH 2/2] chore: add .claude project scaffolding Agent/project config carried alongside the WIP snapshot. Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude/PROJECT.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .claude/PROJECT.md diff --git a/.claude/PROJECT.md b/.claude/PROJECT.md new file mode 100644 index 0000000..03cf53e --- /dev/null +++ b/.claude/PROJECT.md @@ -0,0 +1,33 @@ +# BetLang - Claude Code Instructions + +This repository contains the BetLang programming language compiler and tooling. + +## Project Structure + +``` +betlang/ +├── .claude/ # AI assistant instructions +├── .git/ # Version control +├── .gitignore # Git ignore rules +├── .editorconfig # Editor configuration +└── ... # Compiler files +``` + +## Build Commands + +Refer to project-specific documentation. + +## Coding Conventions + +- Follow hyperpolymath standards +- All code must have SPDX license headers +- Use approved languages only (see CLAUDE.md) +- Document all non-obvious decisions + +## Security + +- No hardcoded secrets +- All secrets through environment variables or secret management +- SHA-pinned dependencies where applicable +- HTTPS only, no HTTP URLs +- No MD5/SHA1 for security purposes