Skip to content

fix(mission-control): headerless journal blocks corrupt the previous entry and missing dates fabricate "now" #841

Description

@adolfousier

The Activity panel showed a 12-day hole with two entries stamped "10s ago". Neither is true: the entries exist in ~/.opencrabs/rsi/improvements.md with correct dates, and the parser misreads them.

Mechanism

src/brain/mission_control/activity_service.rs only opens a new draft on a ## line, and applies any field line to whatever draft is currently open:

if let Some(rest) = line.strip_prefix("## ") {
    ...
    current = Some(EntryDraft::from_header(rest));
} else if let Some(draft) = current.as_mut() {
    apply_field_line(line, draft);   // no guard: applies to the PREVIOUS entry
}

Two compounding defects:

  1. apply_field_line overwrites unconditionally, and a parse failure wipes a good value:
    draft.date = parse_date(rest.trim());   // returns None on an unparseable string
  2. finish() invents a timestamp when none survived:
    let date = self.date.unwrap_or_else(Utc::now);

Evidence

Two non-conforming blocks in a 722-entry journal.

A headerless block (opens with ---, not ## ). Having never opened a draft, all four of its fields land on the preceding entry:

## [Updated] Add rule about accepting user-confirmed data
**Date:** 2026-07-17 12:16 UTC
**Status:** Updated (surgical replace)

---

**Date:** 2026-07-18 (RSI cycle)
**Target:** SOUL.md
**Status:** Applied

2026-07-18 (RSI cycle) fails %Y-%m-%d %H:%M, so parse_date returns None and destroys the good 2026-07-17 date. The later entry is swallowed whole, and the earlier one renders with the wrong rationale under a fabricated timestamp.

A config-sync block with the date in the header and no **Date:** field:

## 2026-07-26 23:33 UTC - config.toml config sync

Date falls back to now; the title becomes the raw date string, which is what the panel displays.

Blast radius: 1 unparseable date, 2 headers without the [Status] prefix, 721 **Date:** lines for 722 headers.

Fix

The parser is one half; the journal having three writers emitting three shapes is the other.

  • Do not let a field line mutate a draft it does not belong to. A **Date:** arriving when the current draft already has one signals a headerless block: flush and start a new draft rather than overwrite.
  • Never overwrite a parsed value with a parse failure.
  • Drop the Utc::now() fallback. An entry with no date should sort and render as undated, not as this instant. Inventing a timestamp is what made a 9-day-old entry look like it happened during the render.
  • Accept the date-in-header shape so config-sync entries carry their real time.

Impact

  • Activity feed shows fabricated "seconds ago" timestamps for entries days or weeks old
  • One journal entry is lost entirely and its fields corrupt a neighbour
  • The apparent gap made a working RSI loop look dead for 12 days

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions