Sign in

    valence

    MoonBit UI where every component is readable by humans and AI equally

    ui
    framework
    moonbit
    reactive
    dual-render
    ai-native
    Download zip
    Version
    0.1.2
    License
    MIT
    Last updated
    4 months ago
    Downloads
    64

    #Valence

    MoonBit UI where every component is readable by humans and AI equally.

    Built on Luna. Every component returns two outputs from one source:

    fn my_panel(state : Signal[Data]) -> (DomNode, () -> String) {
    let visual = div(class="panel") <| [ ... ] // what a human sees
    let narrative = fn() { state.get().describe() } // what an instance reads
    (visual, narrative)
    }

    DomNode renders in the browser. () -> String is a callable narrative — a function, not a snapshot, so it always reflects current state. An instance reads the UI natively. No screenshots, no pixel-guessing, no separate API.

    Interactive components add typed actions:

    fn my_panel(state : Signal[Data]) -> (DomNode, () -> String, PanelActions) {
    // ...
    let actions = PanelActions::{
    available: fn() { [Refresh, Export] }, // what's possible right now
    can_do: fn(a) { ... }, // guard
    on_action: fn(a) { ... }, // execute, return feedback
    }
    (visual, narrative, actions)
    }

    Humans click. Instances call on_action. Same interface, different sensory channel.

    #Zoom

    Zoom is navigation for both perceivers. A human scrolls to change density. An instance picks the detail level it needs. Same state, different rendering:

    pub struct NarrativeRender {
    summary : () -> String // zoomed out — "12 events, 4 instances"
    detail : () -> String // default — "12 events (focused: haiku)"
    full : () -> String // zoomed in — everything
    }

    One surface, not pages. Components persist. Camera moves.

    #Patterns

    Four patterns extracted from real use — each teaches the contract:

    PatternReturnsWhat it does
    status_bar_patternvisual, narrativeRead-only metrics
    instance_panel_patternvisual, narrative, actionsSelectable entity list
    event_stream_patternvisual, narrative, actionsZoom-aware filtered feed
    nav_bar_patternvisual, narrative, actionsTab navigation

    Don't wait for a pattern to exist. Follow the contract in your app — (visual, narrative) — and extract the pattern afterward. That's how these four were built.

    #What Valence is

    A convention library. Thin code (~500 lines), thick contract. Luna handles reactivity (signals, effects, memos). Valence handles meaning — what the UI says about itself, what it can do, and who can perceive it.

    Luna (mizchi/luna) — signals, DOM primitives ↓ Valence (@valence) — dual-render contract, patterns, typed actions ↓ Your components — return (visual, narrative) or (visual, narrative, actions)

    #Install

    moon add helios-house/valence

    #Good fit

    Dashboards, monitoring tools, ops consoles, labs — dense, live, spatial interfaces where a camera moves instead of pages reloading. Anywhere an AI instance should be able to read and operate the same interface a human uses.

    Not a fit: document sites, blogs, anything that's pages not places.

    Component

    type Component = (
    DomNode
    , () -> String)
    Every Valence component returns this tuple:
    • DomNode: visual rendering for humans in the browser
    • () -> String: narrative function for AI instances to read current state

    The narrative is callable so it always reflects current state. It's tested via oracle pattern to ensure it matches the visual.

    ActionDef

    pub struct ActionDef {
    name : String
    description : String
    param_type : String
    }
    Declaration of an action a component can accept Used for AI-readability (instance can read what's possible) and for URL hash mapping (camera targets to actions)

    Event

    pub struct Event {
    timestamp : String
    actor : String
    kind : String
    action : String
    target : String
    source : String
    message : String
    data : String
    }

    EventStreamAction

    pub enum EventStreamAction {
    FocusEntity(String)
    ClearFocus
    }
    Pattern 2: Event Stream Actions Users can focus on a specific actor or clear the focus to see all events

    EventStreamActions

    pub struct EventStreamActions {
    available : () -> Array[EventStreamAction]
    can_do : (EventStreamAction) -> Bool
    on_action : (EventStreamAction) -> PatternActionResult
    }

    InstancePanelAction

    pub enum InstancePanelAction {
    SelectInstance(String)
    ClearSelection
    }
    Pattern 1: Instance Panel Actions Users can select an instance to see its details

    InstancePanelActions

    pub struct InstancePanelActions {
    available : () -> Array[InstancePanelAction]
    can_do : (InstancePanelAction) -> Bool
    on_action : (InstancePanelAction) -> PatternActionResult
    }

    NarrativeRender

    pub struct NarrativeRender {
    summary : () -> String
    detail : () -> String
    full : () -> String
    }
    Narrative with three zoom levels — matching visual zoom levels Used by complex components like event_stream, knowledge view, memory graph
    pub enum NavBarAction {
    SwitchTab(String)
    }
    Pattern 4: Nav Bar Actions Users can switch between tabs
    pub struct NavBarActions {
    available : () -> Array[NavBarAction]
    can_do : (NavBarAction) -> Bool
    on_action : (NavBarAction) -> PatternActionResult
    }

    PatternActionResult

    pub struct PatternActionResult {
    success : Bool
    message : String
    new_narrative : String
    }
    Standard return from on_action for all patterns new_narrative is always detail-level, so caller confirms what happened

    ZoomLevel

    pub enum ZoomLevel {
    ZoomedOut
    Default
    ZoomedIn
    }
    Three density levels for rendering data

    component

    fn component(visual :
    DomNode
    , narrative : () -> String) -> (
    DomNode
    , () -> String)
    Helper: create a component with visual + narrative

    element_exists

    fn element_exists(elem_id : String) -> Bool

    format_relative_time

    fn format_relative_time(timestamp : String, load_time : String) -> String
    Format relative time from ISO timestamp Example: "2026-05-24 14:30:00" with load_time "2026-05-24 14:35:00" → "5 min ago"

    get_narrative

    fn get_narrative(comp : (
    DomNode
    , () -> String)) -> String
    Extract narrative from component

    get_narrative_at_zoom

    fn get_narrative_at_zoom(narrative : NarrativeRender, zoom : ZoomLevel) -> String
    Helper: Get narrative at zoom level

    get_visual

    fn get_visual(comp : (
    DomNode
    , () -> String)) ->
    DomNode
    Extract visual from component

    instance_panel_pattern

    fn instance_panel_pattern(all_events :
    Signal
    [Array[Event]], on_select : (String) -> Unit) -> (
    DomNode
    , () -> String, InstancePanelActions)

    fn nav_bar_pattern(current_tab :
    Signal
    [String]) -> (
    DomNode
    , () -> String, NavBarActions)

    normalize_instance_name

    fn normalize_instance_name(name : String) -> String
    Normalize instance name for use in CSS classes and IDs

    Example: "Claude Code Opus" → "claude-code-opus"

    status_bar_pattern

    fn status_bar_pattern(all_events :
    Signal
    [Array[Event]]) -> (
    DomNode
    , () -> String)

    truncate_string

    fn truncate_string(s : String, max_len : Int) -> String
    Truncate message to max length with ellipsis

    update_element_html

    fn update_element_html(elem_id : String, html : String) -> Unit

    uri_to_key

    fn uri_to_key(uri : String) -> String
    Convert Fluree URI to DOM-safe key (for element IDs, CSS classes)

    Example: "http://helios.local/schema/Heartbeat" → "Heartbeat" "ex:ev-001" → "ev-001"

    zoom_default

    fn zoom_default() -> ZoomLevel
    Helper: Create default zoom level

    zoom_in

    fn zoom_in() -> ZoomLevel
    Helper: Create zoomed in level

    zoom_out

    fn zoom_out() -> ZoomLevel
    Helper: Create zoomed out level

    zoom_to_css_class

    fn zoom_to_css_class(zoom : ZoomLevel) -> String
    Helper: Convert zoom level to CSS class name