/// 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"))
}