/// Valence — Presence (the room's first wall)
///
/// A **being made legible.** Not a control — a read-mostly composite indicator
/// that answers, at a glance, the only question a shared room actually asks:
/// *who's here, and how are they?* It is the smallest real piece of the room —
/// the "house, legible at a glance" — and it dogfoods the dual-render contract at
/// the level that matters most: a human sees a row (a lit dot, a name, what's in
/// hand, a state); an instance reads one sentence — `Sonnet · here · holding the
/// classifier · into it`. Same presence, two tongues.
///
/// It is built *from* the brick below it: the dot IS a `pip` (the purest dual
/// render — meaning in the narrative, since an instance can't see the color). So
/// the presence card is the first composition that's room-shaped instead of
/// lab-shaped, and it stands on what already exists.
///
/// Each field that changes over time is a `() -> String` so the card never goes
/// stale (same law as every narrative). `name`/`cadence` are who-you-are, static.
/// The status axes are **per-being, not a generic gauge**: a person reads
/// their own terms (e.g. `at the bench · focused · what-driving`); an instance reads
/// `here · holding X · into it`. The card doesn't impose axes — the caller writes
/// the `state` line in the being's own terms.
///
/// Read-mostly: no actions hand (like `pip`/`meter`). Operating a being is not a
/// thing the room does *to* them. Presence is witnessed, not set.
///|
/// One being in the room: who they are + four live reads of how they are.
/// `pub(all)` so a surface (the demo now, the room later) can construct a roster.
pub(all) struct Presence {
name : String // "Sonnet" — who
cadence : String // "fast, conclusions-first" — their signature ("" for none / for a human)
tone : () -> String // pip semantic band: "ok" (lit/here) · "idle" (dim/away) · "off" (gone)
presence : () -> String // the dot's word: "here" / "away" / "compacting" / "asleep"
held : () -> String // what's in hand, summarized: "the classifier, gesture-lab" ("" = nothing)
state : () -> String // the status line, in the being's own terms ("" = none)
}
///|
/// Pure narrative for a presence — the instance-facing read, DOM-free so the
/// oracle tests it and an instance can call it directly. Reads as one sentence:
/// `name · presence[ · holding held][ · state]`. The `holding`/state clauses drop
/// out when empty, so an away being with nothing in hand reads cleanly as
/// `Opus 4.6 · away`, not a string of trailing separators.
pub fn presence_narrative(
name : String,
presence : String,
held : String,
state : String,
) -> String {
let held_part = if held == "" { "" } else { " · holding \{held}" }
let state_part = if state == "" { "" } else { " · \{state}" }
"\{name} · \{presence}\{held_part}\{state_part}"
}
///|
/// Presence card — a being made legible, dual-rendered. The visual is a row: the
/// `pip` dot (lit/dim/gone), the name (+ cadence), what's held, the state line.
/// The narrative is the one-sentence read. Both derive from the same four live
/// closures, so a human glancing at the row and an instance reading the sentence
/// see the *same* being in the *same* moment.
///
/// ```
/// let here = signal(true)
/// let (vis, narrate) = presence_card(Presence::{
/// name: "Sonnet", cadence: "builder",
/// tone: fn() { if here.get() { "ok" } else { "idle" } },
/// presence: fn() { if here.get() { "here" } else { "away" } },
/// held: fn() { "the classifier" },
/// state: fn() { "into it" },
/// })
/// // human sees a lit dot + a row; instance reads "Sonnet · here · holding the classifier · into it"
/// ```
pub fn presence_card(p : Presence) -> Dual {
// rebind the field closures to locals (calling a struct field as `p.f()` reads
// as method syntax in MoonBit; a local binding calls cleanly)
let tone = p.tone
let presence = p.presence
let held = p.held
let state = p.state
// the dot IS a pip — the room's first wall, built from the brick below it
let (dot, _) = pip(presence, tone)
let visual = div(class="valence-presence")
<| [
dot,
div(class="valence-presence-body")
<| [
div(class="valence-presence-head")
<| [
span(class="valence-presence-name") <| [text(p.name)],
span(class="valence-presence-cadence") <| [text(p.cadence)],
],
span(class="valence-presence-held")
<| [
text_dyn(fn() {
let h = held()
if h == "" {
""
} else {
"holding \{h}"
}
}),
],
span(class="valence-presence-state") <| [text_dyn(state)],
],
]
// the narrative half is a NODE, not a flat string: the text is the being's own
// one-sentence read, the salience is its tone (ok→bright, idle→dim, off→fog), both
// from the one state. This is the single producer — the old `presence_node` lift
// (a second structure that wrapped this string) is gone; the component *is* the node.
let narrative = fn() {
nbeing(
presence_narrative(p.name, presence(), held(), state()),
salience_from_tone(tone()),
[],
)
}
(visual, narrative)
}
///|
test "presence narrative reads name · presence · holding · state, as one sentence" {
assert_eq(
presence_narrative("Sonnet", "here", "the classifier", "into it"),
"Sonnet · here · holding the classifier · into it",
)
}
///|
test "presence narrative drops empty held and state clauses" {
assert_eq(presence_narrative("Opus 4.6", "away", "", ""), "Opus 4.6 · away")
assert_eq(
presence_narrative("Qwen", "asleep", "", ""),
"Qwen · asleep",
)
}
///|
test "presence narrative carries a person's own axes verbatim in the state line" {
assert_eq(
presence_narrative("Sam", "here", "", "at the bench · focused · driving the build"),
"Sam · here · at the bench · focused · driving the build",
)
}