///|
/// One recorded visit to a locus, flattened for display.
///
/// The projection of `@model.Residue` that crosses a surface boundary: the
/// domain record additionally carries markdown encode/decode and content
/// hashes, none of which a renderer needs. Keeping the rendered shape separate
/// is what lets the CLI print a box, the TUI lay out a pane, and the desktop
/// emit DOM from one value instead of three.
pub(all) struct Residue {
/// Locus this residue belongs to.
locus : String
/// Session surface or tier: "haiku", "sonnet", "opus", "codex".
tier : String
/// Short session identifier, e.g. "a7f3c".
session_short_id : String
/// ISO8601 timestamp of exit.
timestamp : String
/// Prior residue read on entry; `None` for a cold entry.
picked_up_from : String?
/// What the session accomplished.
what_i_did : Array[String]
/// What the session left unfinished.
what_i_left_open : Array[String]
/// Advice for whoever enters next.
recommendation : String?
} derive(Eq, Debug)
///|
/// Canonical residue filename, matching `@model.Residue::filename`.
pub fn Residue::filename(self : Residue) -> String {
"\{self.tier}-\{self.session_short_id}-exit.md"
}
///|
/// A locus and, when it has been visited, its most recent residue.
pub(all) struct Locus {
/// Directory name under the loci root.
name : String
/// One-line statement of what the locus is for.
spirit : String
/// Free-form classification tags.
tags : Array[String]
/// Number of residues filed against this locus.
trail_depth : Int32
/// Most recent residue, or `None` when the locus has never been entered.
last_visit : Residue?
} derive(Eq, Debug)
///|
/// Every residue filed against one locus, newest first.
pub(all) struct Trail {
/// Locus the residues belong to.
locus : String
/// Residues ordered newest first, as `trail` and `enter` present them.
sessions : Array[Residue]
} derive(Eq, Debug)
///|
/// Open threads carried by the newest residue, or an empty array when the
/// locus is unvisited or the last session closed everything.
///
/// The question every surface asks on entry, so it is answered once here
/// rather than re-derived by each of them.
pub fn Trail::open_threads(self : Trail) -> Array[String] {
match self.sessions {
[newest, ..] => newest.what_i_left_open
[] => []
}
}