fix(timeline): report true total_events in robot JSON meta
The robot JSON envelope's meta.total_events field was incorrectly reporting events.len() (the post-limit count), making it identical to meta.showing. This defeated the purpose of having both fields. Changes across the pipeline to fix this: - collect_events now returns (Vec<TimelineEvent>, usize) where the second element is the total event count before truncation - TimelineResult gains a total_events_before_limit field (serde-skipped) so the value flows cleanly from collect through to the renderer - main.rs passes the real total instead of the events.len() workaround Additional cleanup in this pass: - Derive PartialEq/Eq/PartialOrd/Ord on TimelineEventType, replacing the hand-rolled event_type_discriminant() function. Variant declaration order now defines sort tiebreak, documented in a doc comment. - Validate --since input with a proper LoreError::Other instead of silently treating invalid values as None - Fix ANSI-aware tag column padding with console::pad_str (colored tags like "[merged]" were misaligned because ANSI escapes consumed width) - Remove dead print_timeline_json and infer_max_depth functions that were superseded by print_timeline_json_with_meta Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
use console::style;
|
||||
use console::{Alignment, pad_str, style};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::Config;
|
||||
use crate::core::db::create_connection;
|
||||
use crate::core::error::Result;
|
||||
use crate::core::error::{LoreError, Result};
|
||||
use crate::core::paths::get_db_path;
|
||||
use crate::core::project::resolve_project;
|
||||
use crate::core::time::{ms_to_iso, parse_since};
|
||||
@@ -38,7 +38,17 @@ pub fn run_timeline(config: &Config, params: &TimelineParams) -> Result<Timeline
|
||||
.map(|p| resolve_project(&conn, p))
|
||||
.transpose()?;
|
||||
|
||||
let since_ms = params.since.as_deref().and_then(parse_since);
|
||||
let since_ms = params
|
||||
.since
|
||||
.as_deref()
|
||||
.map(|s| {
|
||||
parse_since(s).ok_or_else(|| {
|
||||
LoreError::Other(format!(
|
||||
"Invalid --since value: '{s}'. Use a duration (7d, 2w, 6m) or date (2024-01-15)"
|
||||
))
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// Stage 1+2: SEED + HYDRATE
|
||||
let seed_result = seed_timeline(
|
||||
@@ -60,7 +70,7 @@ pub fn run_timeline(config: &Config, params: &TimelineParams) -> Result<Timeline
|
||||
)?;
|
||||
|
||||
// Stage 4: COLLECT
|
||||
let events = collect_events(
|
||||
let (events, total_before_limit) = collect_events(
|
||||
&conn,
|
||||
&seed_result.seed_entities,
|
||||
&expand_result.expanded_entities,
|
||||
@@ -72,6 +82,7 @@ pub fn run_timeline(config: &Config, params: &TimelineParams) -> Result<Timeline
|
||||
Ok(TimelineResult {
|
||||
query: params.query.clone(),
|
||||
events,
|
||||
total_events_before_limit: total_before_limit,
|
||||
seed_entities: seed_result.seed_entities,
|
||||
expanded_entities: expand_result.expanded_entities,
|
||||
unresolved_references: expand_result.unresolved_references,
|
||||
@@ -125,7 +136,8 @@ fn print_timeline_event(event: &TimelineEvent) {
|
||||
let expanded_marker = if event.is_seed { "" } else { " [expanded]" };
|
||||
|
||||
let summary = truncate_summary(&event.summary, 50);
|
||||
println!("{date} {tag:12} {entity_ref:7} {summary:50} {actor}{expanded_marker}");
|
||||
let tag_padded = pad_str(&tag, 12, Alignment::Left, None);
|
||||
println!("{date} {tag_padded} {entity_ref:7} {summary:50} {actor}{expanded_marker}");
|
||||
|
||||
// Show snippet for evidence notes
|
||||
if let TimelineEventType::NoteEvidence { snippet, .. } = &event.event_type
|
||||
@@ -235,29 +247,6 @@ fn wrap_snippet(text: &str, width: usize) -> Vec<String> {
|
||||
// ─── Robot JSON output ───────────────────────────────────────────────────────
|
||||
|
||||
/// Render timeline as robot-mode JSON in {ok, data, meta} envelope.
|
||||
pub fn print_timeline_json(result: &TimelineResult, total_events_before_limit: usize) {
|
||||
let output = TimelineJsonEnvelope {
|
||||
ok: true,
|
||||
data: TimelineDataJson::from_result(result),
|
||||
meta: TimelineMetaJson {
|
||||
search_mode: "lexical".to_owned(),
|
||||
expansion_depth: infer_max_depth(&result.expanded_entities),
|
||||
expand_mentions: false, // caller should pass this, but we infer from data
|
||||
total_entities: result.seed_entities.len() + result.expanded_entities.len(),
|
||||
total_events: total_events_before_limit,
|
||||
evidence_notes_included: count_evidence_notes(&result.events),
|
||||
unresolved_references: result.unresolved_references.len(),
|
||||
showing: result.events.len(),
|
||||
},
|
||||
};
|
||||
|
||||
match serde_json::to_string(&output) {
|
||||
Ok(json) => println!("{json}"),
|
||||
Err(e) => eprintln!("Error serializing timeline JSON: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extended version that accepts explicit meta values from the caller.
|
||||
pub fn print_timeline_json_with_meta(
|
||||
result: &TimelineResult,
|
||||
total_events_before_limit: usize,
|
||||
@@ -482,10 +471,6 @@ struct TimelineMetaJson {
|
||||
showing: usize,
|
||||
}
|
||||
|
||||
fn infer_max_depth(expanded: &[ExpandedEntityRef]) -> u32 {
|
||||
expanded.iter().map(|e| e.depth).max().unwrap_or(0)
|
||||
}
|
||||
|
||||
fn count_evidence_notes(events: &[TimelineEvent]) -> usize {
|
||||
events
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user