feat(me): improve dashboard rendering with dynamic layout and table-based activity

Overhaul the `lore me` human-mode renderer for better terminal adaptation
and visual clarity:

Layout:
- Add terminal_width() detection (COLUMNS env -> stderr ioctl -> 80 fallback)
- Replace hardcoded column widths with dynamic title_width() that adapts to
  terminal size, clamped to [20, 80]
- Section dividers now span the full terminal width

Activity feed:
- Replace manual println! formatting with Table-based rendering for proper
  column alignment across variable-width content
- Split event_badge() into activity_badge_label() + activity_badge_style()
  for table cell compatibility
- Add system_event_style() (#555555 dark gray) to visually suppress
  non-note events (label, assign, status, milestone, review changes)
- Own actions use dim styling; others' notes render at full color

MR display:
- Add humanize_merge_status() to convert GitLab API values like
  "not_approved" -> "needs approval", "ci_must_pass" -> "CI pending"

Table infrastructure (render.rs):
- Add Table::columns() for headerless tables
- Add Table::indent() for row-level indentation
- Add truncate_pad() for fixed-width cell formatting
- Table::render() now supports headerless mode (no separator line)

Other:
- Default activity lookback changed from 30d to 1d (more useful default)
- Robot-docs schema added for `me` command
- AGENTS.md and CLAUDE.md updated with `lore me` examples

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-21 09:20:25 -05:00
parent 7e9a23cc0f
commit 6e487532aa
7 changed files with 344 additions and 99 deletions

View File

@@ -28,7 +28,15 @@ fn insert_project(conn: &Connection, id: i64, path: &str) {
}
fn insert_issue(conn: &Connection, id: i64, project_id: i64, iid: i64, author: &str) {
insert_issue_with_state(conn, id, project_id, iid, author, "opened");
insert_issue_with_status(
conn,
id,
project_id,
iid,
author,
"opened",
Some("In Progress"),
);
}
fn insert_issue_with_state(
@@ -38,11 +46,30 @@ fn insert_issue_with_state(
iid: i64,
author: &str,
state: &str,
) {
// For closed issues, don't set status_name (they won't appear in dashboard anyway)
let status_name = if state == "opened" {
Some("In Progress")
} else {
None
};
insert_issue_with_status(conn, id, project_id, iid, author, state, status_name);
}
#[allow(clippy::too_many_arguments)]
fn insert_issue_with_status(
conn: &Connection,
id: i64,
project_id: i64,
iid: i64,
author: &str,
state: &str,
status_name: Option<&str>,
) {
let ts = now_ms();
conn.execute(
"INSERT INTO issues (id, gitlab_id, project_id, iid, title, state, author_username, created_at, updated_at, last_seen_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
"INSERT INTO issues (id, gitlab_id, project_id, iid, title, state, status_name, author_username, created_at, updated_at, last_seen_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
rusqlite::params![
id,
id * 10,
@@ -50,6 +77,7 @@ fn insert_issue_with_state(
iid,
format!("Issue {iid}"),
state,
status_name,
author,
ts,
ts,
@@ -552,7 +580,9 @@ fn activity_since_filter() {
let since = now_ms() - 50_000;
let results = query_activity(&conn, "alice", &[], since).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0].body_preview, Some("new comment".to_string()));
// Notes no longer duplicate body into body_preview (summary carries the content)
assert_eq!(results[0].body_preview, None);
assert_eq!(results[0].summary, "new comment");
}
#[test]