- render.rs: clamp flex column width to min(min_flex, natural) instead
of a hardcoded 20, preventing layout overflow when natural width is
small; rewrites flex_width test to be terminal-independent
- list/issues.rs: adopt .flex_col() builder on table construction
- list/mrs.rs, list/notes.rs: consolidate multi-line StyledCell::styled
calls to single-line format
- explain.rs: adopt flex_width() for related-issue title truncation,
consolidate multi-line formatting
Replace hardcoded truncation widths across CLI commands with
render::flex_width() calls that adapt to terminal size. Remove
server-side truncate_to_chars() in timeline collect/seed stages so
full text is preserved through the pipeline — truncation now happens
only at the presentation layer where terminal width is known.
Affected commands: explain, file-history, list (issues/mrs/notes),
me, timeline, who (active/expert/workload).
The explain command's human-mode output was hand-rolled with raw
println! formatting that didn't use any of the shared render.rs
infrastructure. This made it visually inconsistent with every other
command (me, who, search, timeline).
Changes to print_explain():
- Section headers now use render::section_divider() with counts,
producing the same box-drawing divider lines as the me command
- Entity refs use Theme::issue_ref()/mr_ref() color styling
- Entity state uses Theme::state_opened/closed/merged() styling
- Authors/usernames use Theme::username() with @ prefix
- Project paths use Theme::muted()
- Timestamps use format_relative_time() for recency fields (created,
first/last event, last note) and format_date() for point-in-time
fields (key decisions, timeline events), matching the conventions
in me, who, and timeline respectively
- Note excerpts use render::truncate() instead of manual byte slicing
- Related entity titles are truncated via render::truncate()
- Indentation aligned to 4-space content under section dividers
Robot JSON output is unchanged -- it continues to use ms_to_iso() for
all timestamp fields, consistent with the rest of the robot API.
Multiple improvements to the explain command's data richness:
- Add project_path to EntitySummary so consumers can construct URLs from
project + entity_type + iid without extra lookups
- Include first_note_excerpt (first 200 chars) in open threads so agents
and humans get thread context without a separate query
- Add state and direction fields to RelatedIssue — consumers now see
whether referenced entities are open/closed/merged and whether the
reference is incoming or outgoing
- Filter out self-references in both outgoing and incoming related entity
queries (entity referencing itself via cross-reference extraction)
- Wrap timeline excerpt in TimelineExcerpt struct with total_events and
truncated fields — consumers know when events were omitted
- Keep most recent events (tail) instead of oldest (head) when truncating
timeline — recent activity is more actionable
- Floor activity summary first_event at entity created_at — label events
from bulk operations can predate entity creation
- Human output: show project path in header, thread excerpt preview,
state badges on related entities, directional arrows, truncation counts
SQLite does not guarantee row order without ORDER BY, even with LIMIT.
This was a systemic issue found during a multi-pass bug hunt:
Production queries (explain.rs):
- Outgoing reference query: ORDER BY target_entity_type, target_entity_iid
- Incoming reference query: ORDER BY source_entity_type, COALESCE(iid)
Without these, robot mode output was non-deterministic across calls,
breaking clients expecting stable ordering.
Test helper queries (5 locations across 3 files):
- discussions_tests.rs: get_discussion_id()
- mr_discussions.rs: get_mr_discussion_id()
- queue.rs: setup_db_with_job(), release_all_locked_jobs_clears_locks()
Currently safe (single-row inserts) but would break silently if tests
expanded to multi-row fixtures.
1. fetch_open_threads: replace N+1 loop (2 queries per thread) with a
single query using correlated subqueries for note_count and started_by.
2. extract_key_decisions: track consumed notes so the same note is not
matched to multiple events, preventing duplicate decision entries.
3. build_timeline_excerpt_from_pipeline: log tracing::warn on seed/collect
failures instead of silently returning empty timeline.
entity_references.target_entity_iid is nullable (unresolved cross-project
refs), and COALESCE(i.iid, mr.iid) returns NULL for orphaned refs.
Both paths caused rusqlite InvalidColumnType errors when fetching i64.
Added IS NOT NULL filters to both outgoing and incoming reference queries.