One question follows an owner around all day: what is Morna doing right now? The shell answers it in one place, with one vocabulary, in the top bar. It is not a feed and not a spinner — it is four states, and which one you are in is decided by a pure function with no access to anything but three facts.
Four states
The priority order is the whole design, and it is deliberate: needs-you beats working beats blocked beats done — except that a lost connection preempts everything, because a surface that cannot see the truth must not report a comfortable one. Flip between them here; each carries a trigger label, a headline and a detail line, all three from the same resolver.
The resolver is a pure function
deriveWorkStatus() takes three facts and returns the whole view — attention state, trigger label, headline, detail. No component decides its own wording, so the top bar, the drawer and any future surface resolve the same vocabulary from the same input and cannot drift apart. It is also, being pure, the piece that is actually tested.
export function deriveWorkStatus(args: {
needsYou: number;
hasFreshWork: boolean; // an event inside the last 18s
connection: "connected" | "reconnecting" | "lost";
}): WorkStatusView
// the order is the doctrine:
// connection === "lost" → "blocked" — a broken line never looks quiet
// needsYou > 0 → "needs-you" — your decision is never buried
// hasFreshWork || reconnecting → "working"
// otherwise → "done" — "On watch", not "Idle"On watch and headlined “No decision is waiting” — never “Idle”, never “Nothing to do”, never an empty bar. The product is working when you are not looking at it (Law vii), and the status surface is where that claim is either kept or quietly broken.Sol’s brief
The trigger opens a small Drawer — a review beside the work, per the interruption doctrine — holding three lanes: Needs you, Recorded plan, and Recently landed. Each lane has its own count, its own empty sentence, and its own loading sentence, and every one of those sentences is written to state an absence rather than imply a zero: “No owner decision is recorded in the current queue”, “No active plan step is recorded”.
The header is the most interesting part, because of what it refuses to do. It shows the current goal and the active plan step — and then says, in the surface itself: “Source, strategy, authority, and outcome evidence are not recorded in this brief yet.” The API does not return that lineage, so the drawer names the gap instead of assembling a causal story out of whatever data happens to be nearby. That sentence is the design, not a placeholder.
Work trace
WorkTrace is the shipped primitive for the lineage the brief cannot yet show — five glanceable stages answering what changed, why this work was chosen, what is moving, whether the owner is needed, and how success will be witnessed. The rail is always visible; the basis behind each stage is progressive disclosure behind one honest toggle (“Why this work” / “Hide basis”).
| Prop | Type | Notes |
|---|---|---|
stages | WorkTraceStage[] | key: signal | strategy | move | decision | outcome |
stage.state | "recorded" | "current" | "waiting" | "verified" | drives the node icon and its weight — current renders bold |
stage.detail / meta | string? | revealed by disclosure; a stage with neither is rail-only |
defaultOpen | boolean | false — the basis is asked for, never pushed |
WorkTrace ships from @morna/ui with its stylesheet, and it is documented here because it is a real primitive with a settled contract — but no owner surface renders it today, and WorkStatus has a test asserting it does not use it. It is deliberately waiting for the lineage data to exist rather than being wired to a plausible approximation of it.