Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/decodex/src/agent/tracker_tool_bridge/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ fn sample_issue() -> TrackerIssue {
#[cfg(test)]
project_slug: Some(String::from("decodex")),
title: String::from("Sample"),
author: None,
description: String::from("Body"),
priority: Some(3),
created_at: String::from("2026-03-13T04:16:17.133Z"),
Expand Down
1 change: 1 addition & 0 deletions apps/decodex/src/archive_hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ repo_root = "."
#[cfg(test)]
project_slug: Some(String::from("decodex")),
title: format!("Issue {identifier}"),
author: None,
description: String::new(),
priority: None,
created_at: String::from("2026-02-01T00:00:00Z"),
Expand Down
1 change: 1 addition & 0 deletions apps/decodex/src/manual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3058,6 +3058,7 @@ exit 1\n",
#[cfg(test)]
project_slug: None,
title: String::from("Sample issue"),
author: None,
description: String::from(""),
priority: None,
created_at: String::from("2026-04-13T00:00:00Z"),
Expand Down
18 changes: 17 additions & 1 deletion apps/decodex/src/orchestrator/operator_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -5615,6 +5615,16 @@ <h2 id="recent-title">Run History</h2>
return facts.map(([label, value]) => renderRunMetaFact(label, value)).join("");
}

function runAuthor(run) {
return String(run?.author || run?.issue_author || "").trim();
}

function renderRunAuthorInline(run) {
const author = runAuthor(run);

return author ? renderRunMetaFact("author", author) : "";
}

function codexAccount(run) {
const selected = run?.account || run?.codex_account || null;
if (selected) {
Expand Down Expand Up @@ -6486,7 +6496,11 @@ <h2 id="recent-title">Run History</h2>
}

function renderRunMetaLine(run) {
const items = [renderRunCodexAccountInline(run), renderRunTelemetryMetaItems(run)]
const items = [
renderRunAuthorInline(run),
renderRunCodexAccountInline(run),
renderRunTelemetryMetaItems(run),
]
.filter(Boolean)
.join("");

Expand Down Expand Up @@ -8591,6 +8605,7 @@ <h3 class="run-title">${escapeHtml(issueTitle)}</h3>
<summary>Debug Details</summary>
<div class="grid debug-grid">
${field("Run", run.run_id)}
${field("Author", runAuthor(run) || "none")}
${field("Attempt status", run.attempt_status || run.status)}
${field("Updated", formatTimestamp(run.updated_at))}
${field("Codex thread", runThreadSummary(run))}
Expand Down Expand Up @@ -9010,6 +9025,7 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
for (const key of [
"issue_identifier",
"title",
"author",
"account",
"accounts",
"codex_account",
Expand Down
25 changes: 25 additions & 0 deletions apps/decodex/src/orchestrator/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ struct OperatorHistoryLedgerRecord {
struct OperatorIssueDisplayMetadata {
issue_identifier: String,
title: Option<String>,
author: Option<String>,
}

struct WorktreeOwnership {
Expand Down Expand Up @@ -885,6 +886,7 @@ where
OperatorIssueDisplayMetadata {
issue_identifier: issue.identifier,
title: Some(issue.title),
author: issue.author,
},
)
})
Expand Down Expand Up @@ -980,6 +982,9 @@ fn apply_history_lane_issue_metadata(
if let Some(title) = metadata.title.as_ref().filter(|title| !title.trim().is_empty()) {
lane.title = Some(title.clone());
}
if let Some(author) = metadata.author.as_ref().filter(|author| !author.trim().is_empty()) {
lane.author = Some(author.clone());
}
}

fn apply_run_issue_metadata(
Expand All @@ -993,6 +998,9 @@ fn apply_run_issue_metadata(
if let Some(title) = metadata.title.as_ref().filter(|title| !title.trim().is_empty()) {
run.title = Some(title.clone());
}
if let Some(author) = metadata.author.as_ref().filter(|author| !author.trim().is_empty()) {
run.author = Some(author.clone());
}
}

fn fill_missing_history_lane_issue_metadata(
Expand All @@ -1013,6 +1021,11 @@ fn fill_missing_history_lane_issue_metadata(
{
lane.title = Some(title.clone());
}
if lane.author.as_ref().is_none_or(|author| author.trim().is_empty())
&& let Some(author) = metadata.author.as_ref().filter(|author| !author.trim().is_empty())
{
lane.author = Some(author.clone());
}
}

fn fill_missing_run_issue_metadata(
Expand All @@ -1032,6 +1045,11 @@ fn fill_missing_run_issue_metadata(
{
run.title = Some(title.clone());
}
if run.author.as_ref().is_none_or(|author| author.trim().is_empty())
&& let Some(author) = metadata.author.as_ref().filter(|author| !author.trim().is_empty())
{
run.author = Some(author.clone());
}
}

fn hydrate_history_lanes_from_linear_ledger<T>(
Expand Down Expand Up @@ -1083,6 +1101,7 @@ fn hydrate_history_lane_from_ledger_records(
let metadata = OperatorIssueDisplayMetadata {
issue_identifier: record.record.issue_identifier.clone(),
title: None,
author: None,
};

fill_missing_history_lane_issue_metadata(lane, &metadata);
Expand Down Expand Up @@ -1419,6 +1438,7 @@ where
issue_id: issue.id,
issue_identifier: issue.identifier,
title: issue.title,
author: issue.author,
state: issue.state.name,
priority: issue.priority,
created_at: issue.created_at,
Expand Down Expand Up @@ -3362,6 +3382,7 @@ fn operator_run_status(
issue_id: run.issue_id().to_owned(),
issue_identifier,
title: None,
author: None,
attempt_number: run.attempt_number(),
status,
attempt_status: run.status().to_owned(),
Expand Down Expand Up @@ -4094,6 +4115,7 @@ fn operator_history_lanes(
issue_id: run.issue_id.clone(),
issue_identifier: run.issue_identifier.clone(),
title: run.title.clone(),
author: run.author.clone(),
issue_key: operator_run_issue_key(run),
attempt_count: 1,
ledger_outcome: not_loaded_history_ledger_outcome(),
Expand All @@ -4118,6 +4140,9 @@ fn hydrate_history_lane_from_run(lane: &mut OperatorHistoryLaneStatus, run: &Ope
if lane.title.is_none() {
lane.title = run.title.clone();
}
if lane.author.is_none() {
lane.author = run.author.clone();
}
}

fn operator_run_group_key(run: &OperatorRunStatus) -> String {
Expand Down
1 change: 1 addition & 0 deletions apps/decodex/src/orchestrator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ fn sample_issue_with_project_slug_and_sort_fields(
#[cfg(test)]
project_slug: Some(_project_slug.to_owned()),
title: String::from("Implement orchestration"),
author: Some(String::from("Yvette")),
description: String::from("Body"),
priority,
created_at: created_at.to_owned(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ fn operator_dashboard_renders_account_usage_controls() {

assert!(response.contains("function codexAccount(run)"));
assert!(response.contains("function codexAccounts(run)"));
assert!(response.contains("function renderRunAuthorInline(run)"));
assert!(response.contains("function codexAccountDisplayName(account)"));
assert!(response.contains("function codexAccountTokenLabel(refreshStatus)"));
assert!(response.contains("function codexAccountWindowLabel(seconds)"));
Expand Down Expand Up @@ -1496,6 +1497,7 @@ fn operator_dashboard_run_activity_preserves_snapshot_detail_fields() {
assert!(response.contains("function snapshotWithLiveRunActivity(snapshot)"));
assert!(response.contains("\"issue_identifier\""));
assert!(response.contains("\"title\""));
assert!(response.contains("\"author\""));
assert!(response.contains("\"child_agent_activity\""));
assert!(response.contains("\"protocol_activity\""));
assert!(response.contains("!dashboardRunFieldHasValue(activityRun[key])"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,17 @@ fn live_operator_status_snapshot_hydrates_active_run_issue_display_metadata() {
assert_eq!(active_run.project_id, config.service_id());
assert_eq!(active_run.issue_identifier.as_deref(), Some("XY-392"));
assert_eq!(active_run.title.as_deref(), Some("Hydrate issue display metadata on run rows"));
assert_eq!(active_run.author.as_deref(), Some("Yvette"));
assert_eq!(recent_run.issue_identifier.as_deref(), Some("XY-392"));
assert_eq!(recent_run.title.as_deref(), Some("Hydrate issue display metadata on run rows"));
assert_eq!(recent_run.author.as_deref(), Some("Yvette"));
assert_eq!(snapshot_json["active_runs"][0]["project_id"], "pubfi");
assert_eq!(snapshot_json["active_runs"][0]["issue_identifier"], "XY-392");
assert_eq!(
snapshot_json["active_runs"][0]["title"],
"Hydrate issue display metadata on run rows"
);
assert_eq!(snapshot_json["active_runs"][0]["author"], "Yvette");
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ fn operator_status_text_active_run() -> orchestrator::OperatorRunStatus {
issue_id: String::from("issue-1"),
issue_identifier: Some(String::from("PUB-101")),
title: Some(String::from("Implement orchestration")),
author: Some(String::from("Yvette")),
attempt_number: 1,
status: String::from("running"),
attempt_status: String::from("running"),
Expand Down Expand Up @@ -312,6 +313,7 @@ fn operator_status_text_queued_candidates() -> Vec<OperatorQueuedIssueStatus> {
issue_id: String::from("issue-1"),
issue_identifier: String::from("PUB-101"),
title: String::from("Running lane still has a backlog claim"),
author: Some(String::from("Yvette")),
state: String::from("In Progress"),
priority: Some(1),
created_at: String::from("2026-03-14T09:57:00Z"),
Expand All @@ -324,6 +326,7 @@ fn operator_status_text_queued_candidates() -> Vec<OperatorQueuedIssueStatus> {
issue_id: String::from("issue-2"),
issue_identifier: String::from("PUB-102"),
title: String::from("Implement backlog surface"),
author: Some(String::from("Yvette")),
state: String::from("Todo"),
priority: Some(2),
created_at: String::from("2026-03-14T09:58:00Z"),
Expand All @@ -336,6 +339,7 @@ fn operator_status_text_queued_candidates() -> Vec<OperatorQueuedIssueStatus> {
issue_id: String::from("issue-5"),
issue_identifier: String::from("PUB-105"),
title: String::from("Remove stale queue label"),
author: Some(String::from("Yvette")),
state: String::from("Done"),
priority: Some(3),
created_at: String::from("2026-03-14T09:59:00Z"),
Expand Down
3 changes: 3 additions & 0 deletions apps/decodex/src/orchestrator/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,7 @@ struct OperatorHistoryLaneStatus {
issue_id: String,
issue_identifier: Option<String>,
title: Option<String>,
author: Option<String>,
issue_key: String,
attempt_count: usize,
ledger_outcome: OperatorHistoryLedgerOutcome,
Expand Down Expand Up @@ -753,6 +754,7 @@ struct OperatorRunStatus {
issue_id: String,
issue_identifier: Option<String>,
title: Option<String>,
author: Option<String>,
attempt_number: i64,
status: String,
attempt_status: String,
Expand Down Expand Up @@ -801,6 +803,7 @@ struct OperatorQueuedIssueStatus {
issue_id: String,
issue_identifier: String,
title: String,
author: Option<String>,
state: String,
priority: Option<i64>,
created_at: String,
Expand Down
1 change: 1 addition & 0 deletions apps/decodex/src/tracker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub(crate) struct TrackerIssue {
#[cfg(test)]
pub(crate) project_slug: Option<String>,
pub(crate) title: String,
pub(crate) author: Option<String>,
pub(crate) description: String,
pub(crate) priority: Option<i64>,
pub(crate) created_at: String,
Expand Down
47 changes: 46 additions & 1 deletion apps/decodex/src/tracker/linear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ query IssuesWithLabel($labelName: String!, $after: String) {
id
identifier
title
creator {
displayName
name
email
}
description
priority
createdAt
Expand Down Expand Up @@ -85,6 +90,11 @@ query IssueByIdentifier($issueIdentifier: String!) {
id
identifier
title
creator {
displayName
name
email
}
description
priority
createdAt
Expand Down Expand Up @@ -146,6 +156,11 @@ query IssuesByIds($issueIds: [ID!], $after: String) {
id
identifier
title
creator {
displayName
name
email
}
description
priority
createdAt
Expand Down Expand Up @@ -711,6 +726,7 @@ struct LinearIssue {
id: String,
identifier: String,
title: String,
creator: Option<LinearUser>,
description: Option<String>,
priority: Option<i64>,
#[serde(rename = "createdAt")]
Expand All @@ -732,6 +748,14 @@ struct LinearTeam {
labels: LabelConnection,
}

#[derive(Deserialize)]
struct LinearUser {
#[serde(rename = "displayName")]
display_name: Option<String>,
name: Option<String>,
email: Option<String>,
}

#[derive(Deserialize)]
struct StateConnection {
nodes: Vec<LinearState>,
Expand Down Expand Up @@ -893,12 +917,15 @@ fn map_blockers(relations: &[LinearIssueRelation]) -> Vec<TrackerIssueBlocker> {
}

fn map_issue(issue: LinearIssue, blockers: Vec<TrackerIssueBlocker>) -> TrackerIssue {
let author = linear_user_display_name(issue.creator.as_ref());

TrackerIssue {
id: issue.id,
identifier: issue.identifier,
#[cfg(test)]
project_slug: None,
title: issue.title,
author,
description: issue.description.unwrap_or_default(),
priority: issue.priority,
created_at: issue.created_at,
Expand Down Expand Up @@ -933,13 +960,25 @@ fn map_issue(issue: LinearIssue, blockers: Vec<TrackerIssueBlocker>) -> TrackerI
}
}

fn linear_user_display_name(user: Option<&LinearUser>) -> Option<String> {
let user = user?;

[&user.display_name, &user.name, &user.email]
.into_iter()
.filter_map(|value| value.as_deref())
.map(str::trim)
.find(|value| !value.is_empty())
.map(str::to_owned)
}

#[cfg(test)]
mod tests {
use serde_json::json;

use crate::tracker::linear::{
GraphqlError, IssueRelationConnection, LabelConnection, LinearIssue, LinearIssueRelation,
LinearLabel, LinearRelatedIssue, LinearState, LinearTeam, PageInfo, StateConnection,
LinearLabel, LinearRelatedIssue, LinearState, LinearTeam, LinearUser, PageInfo,
StateConnection,
};

#[test]
Expand All @@ -948,6 +987,11 @@ mod tests {
id: String::from("issue-1"),
identifier: String::from("PUB-101"),
title: String::from("Implement ordering"),
creator: Some(LinearUser {
display_name: Some(String::from("Yvette")),
name: Some(String::from("yvette")),
email: Some(String::from("yvette@example.com")),
}),
description: Some(String::from("Body")),
priority: Some(2),
created_at: String::from("2026-03-13T04:16:17.133Z"),
Expand Down Expand Up @@ -996,6 +1040,7 @@ mod tests {
let mapped = super::map_issue(issue, blockers);

assert_eq!(mapped.priority, Some(2));
assert_eq!(mapped.author.as_deref(), Some("Yvette"));
assert_eq!(mapped.created_at, "2026-03-13T04:16:17.133Z");
assert_eq!(mapped.updated_at, "2026-03-14T04:16:17.133Z");
assert_eq!(mapped.blockers.len(), 1);
Expand Down