/// Valence — the feed: a salience-tagged tail of lines, dual-rendered.
///
/// The archetype under half the house's panels: the lab's session feed, the
/// demo's event log, the atrium floor's chat tail / IRC porch / events tail —
/// all "the last N lines of something, newest meaningful, older sinking."
/// Extracted 2026-07-17 (fourth hand-rolled copy was the tell); built on
/// `list_dual`, so the stale-slot law and the atomic swap come free.
///
/// A feed is a *sequence* (order is meaning — usually time), so the narrative
/// keeps given order; salience still drives brightness/typography per line and
/// makes any wire cut safe (HF1). The caller decides order (newest-first or
/// oldest-first) by how it builds the array.
///|
/// One feed line: stable identity (for the row swap), the text, its salience.
/// `meta` renders dim before the text when non-empty (a timestamp, an actor).
pub(all) struct FeedLine {
key : String
meta : String
text : String
salience : Double
}
///|
/// Pure narrative for a feed — DOM-free, oracle-testable, callable directly
/// by an instance that wants the tail without the pixels.
pub fn feed_line_node(l : FeedLine) -> NarrativeNode {
let t = if l.meta == "" { l.text } else { "\{l.meta} \{l.text}" }
ntext(t, l.salience)
}
///|
/// The feed as a Dual. `lines` closes over your signals (reading them inside
/// is what makes it live).
pub fn feed(container_id : String, lines : () -> Array[FeedLine]) -> Dual {
list_dual(
container_id,
lines,
fn(l : FeedLine) { l.key },
fn(l : FeedLine) {
let meta = if l.meta == "" {
""
} else {
"" + escape_html(l.meta) + " "
}
meta + "" + escape_html(l.text) + ""
},
feed_line_node,
)
}
// ── Oracle (pure half only — the Dual needs a document, same as list_dual) ──
///|
test "a feed narrates its lines in given order with their salience" {
let lines : Array[FeedLine] = [
{ key: "1", meta: "10:02", text: "first thing", salience: 0.8 },
{ key: "2", meta: "", text: "second thing", salience: 0.4 },
]
let s = render_narrative(list_narrative(lines, feed_line_node))
// order kept (sequence), meta joined, both lines present
assert_true(s.contains("10:02 first thing"))
assert_true(s.contains("second thing"))
}