chore: record panel followups
This commit is contained in:
parent
b6685af3ae
commit
8cbade818f
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: "Workspace panel Companion interface"
|
||||
state: "planning"
|
||||
state: 'closed'
|
||||
created_at: "2026-06-07T00:16:51Z"
|
||||
updated_at: "2026-06-07T03:13:01Z"
|
||||
updated_at: '2026-06-18T13:06:31Z'
|
||||
---
|
||||
|
||||
## Background
|
||||
|
|
|
|||
10
.yoi/tickets/00001KTFQ109S/resolution.md
Normal file
10
.yoi/tickets/00001KTFQ109S/resolution.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
Closed as completed by child Tickets.
|
||||
|
||||
The original Workspace Panel Companion interface plan has been implemented through more specific work:
|
||||
- direct selected-Pod send was removed from the Panel composer path;
|
||||
- Panel composer routing now targets the workspace Companion and Ticket Intake explicitly;
|
||||
- workspace Companion Pod lifecycle restore/spawn/observe behavior is implemented;
|
||||
- local role/session registry and Ticket claim handling were added for Panel-launched role sessions;
|
||||
- project role Profile feature defaults limit Companion authority and keep Ticket orchestration / Pods / Task disabled for Companion by default.
|
||||
|
||||
The remaining work in this area should be tracked as targeted follow-up Tickets rather than keeping this umbrella planning Ticket open.
|
||||
|
|
@ -74,4 +74,31 @@ Companion work is useful but not required for near-term panel operation. The pan
|
|||
|
||||
Decision: downgrade Companion-related follow-up priority to P2 so near-term focus can stay on Ticket role config strictness/init, Orchestrator queue automation, and workflow/compaction reliability.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: hare at: 2026-06-18T13:06:31Z from: planning to: closed reason: closed field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket を closed にしました。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: close author: hare at: 2026-06-18T13:06:31Z status: closed -->
|
||||
|
||||
## 完了
|
||||
|
||||
Closed as completed by child Tickets.
|
||||
|
||||
The original Workspace Panel Companion interface plan has been implemented through more specific work:
|
||||
- direct selected-Pod send was removed from the Panel composer path;
|
||||
- Panel composer routing now targets the workspace Companion and Ticket Intake explicitly;
|
||||
- workspace Companion Pod lifecycle restore/spawn/observe behavior is implemented;
|
||||
- local role/session registry and Ticket claim handling were added for Panel-launched role sessions;
|
||||
- project role Profile feature defaults limit Companion authority and keep Ticket orchestration / Pods / Task disabled for Companion by default.
|
||||
|
||||
The remaining work in this area should be tracked as targeted follow-up Tickets rather than keeping this umbrella planning Ticket open.
|
||||
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
title: 'Plugin: package discovery and explicit enablement resolver'
|
||||
state: 'done'
|
||||
state: 'closed'
|
||||
created_at: '2026-06-15T13:40:15Z'
|
||||
updated_at: '2026-06-15T15:30:00Z'
|
||||
updated_at: '2026-06-18T12:22:04Z'
|
||||
assignee: null
|
||||
readiness: 'implementation_ready'
|
||||
risk_flags: ['plugin', 'package-loading', 'discovery', 'enablement', 'capability-boundary', 'startup-restore']
|
||||
|
|
|
|||
3
.yoi/tickets/00001KV5R5V2S/resolution.md
Normal file
3
.yoi/tickets/00001KV5R5V2S/resolution.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Ticket `00001KV5R5V2S` (`Plugin: package discovery and explicit enablement resolver`) はすでに `state: done` に到達していたため、workspace Panel から close しました。
|
||||
|
||||
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
|
||||
|
|
@ -524,4 +524,24 @@ Cleanup planned:
|
|||
|
||||
Reviewer approved after requested fixes, implementation branch merged into the orchestration branch, and focused plus packaging validation passed in the Orchestrator worktree. Marking Ticket done in the orchestration branch.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: state_changed author: hare at: 2026-06-18T12:22:04Z from: done to: closed reason: closed field: state -->
|
||||
|
||||
## State changed
|
||||
|
||||
Ticket を closed にしました。
|
||||
|
||||
|
||||
---
|
||||
|
||||
<!-- event: close author: hare at: 2026-06-18T12:22:04Z status: closed -->
|
||||
|
||||
## 完了
|
||||
|
||||
Ticket `00001KV5R5V2S` (`Plugin: package discovery and explicit enablement resolver`) はすでに `state: done` に到達していたため、workspace Panel から close しました。
|
||||
|
||||
この Close action によって、実装作業、state 変更、Orchestrator/Companion launch、worker invocation は開始されていません。
|
||||
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
title: 'Panel startup latency E2E を一覧データ描画完了基準に修正する'
|
||||
state: 'done'
|
||||
created_at: '2026-06-15T16:44:06Z'
|
||||
updated_at: '2026-06-18T12:25:14Z'
|
||||
updated_at: '2026-06-18T13:30:51Z'
|
||||
assignee: null
|
||||
readiness: 'implementation_ready'
|
||||
risk_flags: ['panel', 'e2e', 'startup-latency', 'readiness-metric', 'ticket-list-rendering']
|
||||
|
|
|
|||
|
|
@ -281,4 +281,17 @@ Cleanup planned:
|
|||
|
||||
Reviewer approved, implementation branch merged into the orchestration branch, and E2E-focused validation passed in the Orchestrator worktree. Marking Ticket done in the orchestration branch.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: review author: hare at: 2026-06-18T13:30:51Z status: request_changes -->
|
||||
|
||||
## Review: request changes
|
||||
|
||||
Request changes.
|
||||
|
||||
The current result still does not answer the user-facing latency problem. The problematic latency is the time from launching `yoi panel` / pressing Enter to seeing the actual workspace dashboard content. The current E2E measures a direct subprocess spawn to one concrete fixture Ticket row appearing in `rows_rendered`; it does not require the dashboard content to be complete from the user's perspective, and it does not reproduce or attribute the clearly long live-workspace delay.
|
||||
|
||||
Do not treat fixture first-frame or single-row readiness numbers as evidence that no improvement is needed. The acceptance criterion must be strengthened to a user-visible dashboard-content-ready point and paired with slow-source attribution/improvement for the live-like Panel startup path.
|
||||
|
||||
|
||||
---
|
||||
|
|
|
|||
0
.yoi/tickets/00001KVDETSN6/artifacts/.gitkeep
Normal file
0
.yoi/tickets/00001KVDETSN6/artifacts/.gitkeep
Normal file
29
.yoi/tickets/00001KVDETSN6/artifacts/relations.json
Normal file
29
.yoi/tickets/00001KVDETSN6/artifacts/relations.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"version": 1,
|
||||
"relations": [
|
||||
{
|
||||
"ticket_id": "00001KVDETSN6",
|
||||
"kind": "related",
|
||||
"target": "00001KV5D7MG5",
|
||||
"note": "Dashboard content-ready fixture should include orchestration overlay state.",
|
||||
"author": "yoi ticket",
|
||||
"at": "2026-06-18T13:31:43Z"
|
||||
},
|
||||
{
|
||||
"ticket_id": "00001KVDETSN6",
|
||||
"kind": "related",
|
||||
"target": "00001KV5MRH6D",
|
||||
"note": "Follows up Panel startup latency E2E work.",
|
||||
"author": "yoi ticket",
|
||||
"at": "2026-06-18T13:31:43Z"
|
||||
},
|
||||
{
|
||||
"ticket_id": "00001KVDETSN6",
|
||||
"kind": "related",
|
||||
"target": "00001KV62PF32",
|
||||
"note": "Supersedes the insufficient single-row rows-ready E2E with user-visible dashboard content-ready measurement.",
|
||||
"author": "yoi ticket",
|
||||
"at": "2026-06-18T13:31:43Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
103
.yoi/tickets/00001KVDETSN6/item.md
Normal file
103
.yoi/tickets/00001KVDETSN6/item.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
---
|
||||
title: 'Panel startup latency をユーザー目線の dashboard content ready 基準で計測・改善する'
|
||||
state: 'ready'
|
||||
created_at: '2026-06-18T13:30:51Z'
|
||||
updated_at: '2026-06-18T13:31:43Z'
|
||||
assignee: null
|
||||
readiness: 'implementation_ready'
|
||||
risk_flags: ['panel', 'e2e', 'startup-latency', 'user-visible-readiness', 'dashboard-content', 'profiling']
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
ユーザーが問題にしている `yoi panel` startup latency は、first frame や fixture の単一 Ticket row が `rows_rendered` に出るまでではなく、**ユーザーが `yoi panel` を起動してから、workspace dashboard として実際に使えるコンテンツが画面に揃って見えるまで**の時間である。
|
||||
|
||||
既存の `00001KV62PF32` は `panel_ready` / first frame と単一 fixture Ticket row readiness の混同を修正したが、まだ以下の点で不十分だった。
|
||||
|
||||
- direct subprocess spawn から単一 fixture Ticket row の `rows_rendered` までを測っているだけで、workspace dashboard 全体の content ready ではない。
|
||||
- live workspace でユーザーが体感している明らかに長い遅延を再現・属性分解していない。
|
||||
- fixture 上の約 120ms rows-ready をもって「追加改善不要」と判断してしまうと、ユーザー視点の問題を取り逃がす。
|
||||
|
||||
この Ticket では、Panel startup latency の主基準を user-visible dashboard content ready に置き直し、遅延源を計測・改善する。
|
||||
|
||||
## Definitions
|
||||
|
||||
- `panel_first_frame`: 初回 visible draw。loading / empty frame でもよい補助 metric。
|
||||
- `fixture_single_row_ready`: 具体的な fixture Ticket row が `rows_rendered` に現れる補助 metric。
|
||||
- `dashboard_content_ready`: ユーザーが workspace dashboard として必要な主要コンテンツが揃い、実際に画面へ描画された状態。この Ticket の主 metric。
|
||||
|
||||
`dashboard_content_ready` は少なくとも以下を含む。
|
||||
|
||||
- Ticket rows が fixture / live-like workspace の期待データと一致している。
|
||||
- id
|
||||
- title
|
||||
- state/status
|
||||
- row kind
|
||||
- primary action / disabled reason where relevant
|
||||
- Pod / Companion / Orchestrator 関連 row または status が、fixture / live-like workspace の期待状態と一致している。
|
||||
- orchestration overlay を含む fixture では、local / orchestration state が表示上も期待通り反映されている。
|
||||
- loading / empty / partial single-row render だけでは ready とみなさない。
|
||||
|
||||
## Requirements
|
||||
|
||||
- E2E / harness の readiness event または helper を追加・修正し、`dashboard_content_ready` を測れるようにする。
|
||||
- first frame / single-row readiness とは別 metric にする。
|
||||
- event 名・test 名・log 出力から意味が誤解されないようにする。
|
||||
- 測定開始点は、ユーザーの `yoi panel` 起動に十分近いものにする。
|
||||
- 基本は `Command::spawn` 直前からでよい。
|
||||
- interactive shell 入力まで含めない場合は、その範囲を test/report に明記する。
|
||||
- Fixture を live-like に強化する。
|
||||
- 複数 Ticket state を含める。
|
||||
- Pod metadata / Companion / Orchestrator 表示を含める。
|
||||
- orchestration overlay を含める。
|
||||
- 必要に応じて stale socket / slow observation / many Ticket records など、実遅延の候補を再現する fixture を追加する。
|
||||
- `dashboard_content_ready` は単なる `rows.len() >= N` や単一 Ticket row match だけで通さない。
|
||||
- expected dashboard snapshot / expected row set として比較する。
|
||||
- 欠落 row、wrong status、wrong action、overlay 未反映を fail にする。
|
||||
- Live workspace 相当の遅延源を属性分解する。
|
||||
- Ticket scan / parsing
|
||||
- orchestration overlay worktree validation / read
|
||||
- Pod metadata scan
|
||||
- socket/status probing
|
||||
- Companion / Orchestrator lifecycle observation
|
||||
- role session / local claim scan
|
||||
- git worktree / branch checks
|
||||
- 明らかに長い遅延がある場合は改善する。
|
||||
- UI 初期化を content-ready 待ちで止めないだけでは不十分。
|
||||
- 実コンテンツが揃うまでの経路自体を短くする。
|
||||
- slow source を lazy / bounded / parallel / cached / timeout-shortened にできる場合は実装する。
|
||||
- Before / after の実測値を implementation report に記録する。
|
||||
- first frame
|
||||
- dashboard content ready
|
||||
- slow-source breakdown
|
||||
- fixture 条件 / live-like 条件
|
||||
- 測定で改善不要と判断する場合でも、ユーザーが見ている長い live latency がなぜ再現しないか、またはどの範囲外かを明示する。
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- E2E が `dashboard_content_ready` を主 startup latency metric として測る。
|
||||
- `panel_first_frame` または単一 Ticket row readiness だけでは、この Ticket の主 E2E は通らない。
|
||||
- Expected dashboard snapshot に含まれる Ticket / Pod / Companion / Orchestrator / overlay 要素が揃って描画された時点を ready として扱う。
|
||||
- Missing row / wrong state / missing overlay / missing action label の fixture では ready 判定が fail する。
|
||||
- User-visible dashboard content ready の before / after 実測値が記録される。
|
||||
- 遅延源の breakdown が記録され、主要 slow source に対して具体的な改善または明示的な non-action rationale がある。
|
||||
- Live-like fixture または current workspace に近い条件で、ユーザー体感の長い遅延を取り逃がさない。
|
||||
- Existing Panel behavior に regression がない。
|
||||
- row selection
|
||||
- composer target
|
||||
- Queue action
|
||||
- orchestration overlay display
|
||||
- Validation: relevant `cargo test -p yoi-e2e --features e2e panel`, `cargo check`, `cargo fmt --check`, `git diff --check`, and `nix build .#yoi` if code/package/runtime behavior changes.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Interactive shell の command lookup / prompt rendering まで含めた OS/shell latency の厳密測定。
|
||||
- すべての background observation が完全 settle するまで UI を出さないこと。
|
||||
- Panel architecture の全面刷新。
|
||||
- Ticket lifecycle semantics の変更。
|
||||
|
||||
## Related work
|
||||
|
||||
- `00001KV62PF32` — Panel startup latency E2E を一覧データ描画完了基準に修正する。単一 fixture row readiness までで不十分だったため request-changes 済み。
|
||||
- `00001KV5MRH6D` — Panel startup latency E2E / first visible frame separation work。
|
||||
- `00001KV5D7MG5` — Panel orchestration worktree Ticket state overlay。
|
||||
7
.yoi/tickets/00001KVDETSN6/thread.md
Normal file
7
.yoi/tickets/00001KVDETSN6/thread.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<!-- event: create author: "yoi ticket" at: 2026-06-18T13:30:51Z -->
|
||||
|
||||
## 作成
|
||||
|
||||
LocalTicketBackend によって作成されました。
|
||||
|
||||
---
|
||||
|
|
@ -5406,6 +5406,9 @@ fn panel_ticket_detail(row: &PanelRow) -> String {
|
|||
}
|
||||
|
||||
let mut parts = vec![panel_ticket_reference(row)];
|
||||
if let Some(overlay_detail) = panel_ticket_overlay_detail(row) {
|
||||
parts.push(overlay_detail);
|
||||
}
|
||||
if let Some(blocked_reason) = row
|
||||
.ticket
|
||||
.as_ref()
|
||||
|
|
@ -5441,6 +5444,24 @@ fn panel_ticket_action_label(row: &PanelRow, action: NextUserAction) -> &'static
|
|||
}
|
||||
}
|
||||
|
||||
fn panel_ticket_overlay_detail(row: &PanelRow) -> Option<String> {
|
||||
let ticket = row.ticket.as_ref()?;
|
||||
let overlay = ticket.orchestration_overlay.as_ref()?;
|
||||
let mut detail = format!(
|
||||
"Overlay: local {} · {} {}",
|
||||
ticket.workflow_state.as_str(),
|
||||
overlay.source,
|
||||
overlay.workflow_state.as_str()
|
||||
);
|
||||
if matches!(
|
||||
overlay.workflow_state,
|
||||
TicketWorkflowState::Done | TicketWorkflowState::Closed
|
||||
) {
|
||||
detail.push_str(" · merge pending");
|
||||
}
|
||||
Some(detail)
|
||||
}
|
||||
|
||||
fn panel_ticket_reason(row: &PanelRow) -> Option<&str> {
|
||||
row.disabled_reason
|
||||
.as_deref()
|
||||
|
|
@ -7596,6 +7617,43 @@ branch = "orchestration/custom-panel"
|
|||
assert!(detail_line.ends_with('…'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panel_orchestration_overlay_uses_compact_status_column_and_detail_line() {
|
||||
let mut row = panel_test_ticket_row(
|
||||
"00001OVERLAY",
|
||||
"Overlay column regression",
|
||||
ActionPriority::Background,
|
||||
NextUserAction::Wait,
|
||||
"queued",
|
||||
);
|
||||
row.kind = PanelRowKind::Review;
|
||||
row.status = "q→done".to_string();
|
||||
row.disabled_reason = Some(
|
||||
"orchestration worktree overlay shows Ticket state done; local state remains queued"
|
||||
.to_string(),
|
||||
);
|
||||
row.ticket.as_mut().unwrap().orchestration_overlay =
|
||||
Some(crate::workspace_panel::TicketStateOverlay {
|
||||
source: "orchestration".to_string(),
|
||||
workflow_state: TicketWorkflowState::Done,
|
||||
});
|
||||
|
||||
let lines = panel_row_lines(&row, false, 160);
|
||||
let title_line = plain_line(&lines[0]);
|
||||
let detail_line = plain_line(&lines[1]);
|
||||
let state_start = 2;
|
||||
let title_start = state_start + TICKET_STATE_COLUMN_WIDTH + 1;
|
||||
|
||||
assert!(row.status.width() <= TICKET_STATE_COLUMN_WIDTH);
|
||||
assert_eq!(display_column(&title_line, "q→done"), state_start);
|
||||
assert_eq!(
|
||||
display_column(&title_line, "Overlay column regression"),
|
||||
title_start
|
||||
);
|
||||
assert!(!title_line.contains("orchestration"));
|
||||
assert!(detail_line.contains("Overlay: local queued · orchestration done · merge pending"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ready_ticket_with_waiting_gate_shows_queue_disabled_reason() {
|
||||
let mut row = panel_test_ticket_row(
|
||||
|
|
|
|||
|
|
@ -1089,15 +1089,25 @@ fn ticket_state_display(
|
|||
) -> String {
|
||||
match overlay {
|
||||
Some(overlay) => format!(
|
||||
"local: {} · {}: {}",
|
||||
local.as_str(),
|
||||
overlay.source,
|
||||
overlay.workflow_state.as_str()
|
||||
"{}→{}",
|
||||
compact_ticket_state_label(local),
|
||||
compact_ticket_state_label(overlay.workflow_state)
|
||||
),
|
||||
None => local.as_str().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn compact_ticket_state_label(state: TicketWorkflowState) -> &'static str {
|
||||
match state {
|
||||
TicketWorkflowState::Planning => "plan",
|
||||
TicketWorkflowState::Ready => "ready",
|
||||
TicketWorkflowState::Queued => "q",
|
||||
TicketWorkflowState::InProgress => "prog",
|
||||
TicketWorkflowState::Done => "done",
|
||||
TicketWorkflowState::Closed => "cls",
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_orchestration_overlay_to_derived(
|
||||
derived: &mut DerivedTicketState,
|
||||
local: TicketWorkflowState,
|
||||
|
|
@ -1138,51 +1148,90 @@ fn apply_orchestration_overlay_to_derived(
|
|||
}
|
||||
}
|
||||
|
||||
fn format_relation_blockers(blockers: &[&TicketRelationBlocker]) -> String {
|
||||
let shown_blockers = blockers.iter().take(3).count();
|
||||
let mut formatted = blockers
|
||||
.iter()
|
||||
.take(3)
|
||||
.map(|blocker| {
|
||||
format!(
|
||||
"{} via {} (state: {})",
|
||||
blocker.blocking_ticket,
|
||||
blocker.reason_kind,
|
||||
blocker.blocking_state.as_str()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let remaining_blockers = blockers.len().saturating_sub(shown_blockers);
|
||||
if remaining_blockers > 0 {
|
||||
formatted.push_str(&format!(" (+{remaining_blockers} more)"));
|
||||
}
|
||||
formatted
|
||||
}
|
||||
|
||||
fn relation_blocker_allows_ready_queue(blocker: &TicketRelationBlocker) -> bool {
|
||||
matches!(
|
||||
blocker.blocking_state,
|
||||
TicketWorkflowState::Queued | TicketWorkflowState::InProgress
|
||||
)
|
||||
}
|
||||
|
||||
fn derive_ticket_state(
|
||||
summary: &TicketSummary,
|
||||
relation_blockers: &[TicketRelationBlocker],
|
||||
) -> DerivedTicketState {
|
||||
if !relation_blockers.is_empty() {
|
||||
let shown_blockers = relation_blockers.iter().take(3).count();
|
||||
let mut blockers = relation_blockers
|
||||
let active_blockers = relation_blockers
|
||||
.iter()
|
||||
.take(3)
|
||||
.map(|blocker| {
|
||||
format!(
|
||||
"{} via {} (state: {})",
|
||||
blocker.blocking_ticket,
|
||||
blocker.reason_kind,
|
||||
blocker.blocking_state.as_str()
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let remaining_blockers = relation_blockers.len().saturating_sub(shown_blockers);
|
||||
if remaining_blockers > 0 {
|
||||
blockers.push_str(&format!(" (+{remaining_blockers} more)"));
|
||||
.filter(|blocker| !relation_blocker_allows_ready_queue(blocker))
|
||||
.collect::<Vec<_>>();
|
||||
if !active_blockers.is_empty() || summary.workflow_state != TicketWorkflowState::Ready {
|
||||
let blockers_to_report = if active_blockers.is_empty() {
|
||||
relation_blockers.iter().collect::<Vec<_>>()
|
||||
} else {
|
||||
active_blockers
|
||||
};
|
||||
let blockers = format_relation_blockers(&blockers_to_report);
|
||||
let waiting_reason = format!("waiting for {blockers}");
|
||||
return DerivedTicketState {
|
||||
kind: match summary.workflow_state {
|
||||
TicketWorkflowState::Planning => PanelRowKind::Planning,
|
||||
TicketWorkflowState::Queued | TicketWorkflowState::InProgress => {
|
||||
PanelRowKind::ActiveWork
|
||||
}
|
||||
TicketWorkflowState::Done | TicketWorkflowState::Closed => PanelRowKind::Review,
|
||||
TicketWorkflowState::Ready => PanelRowKind::Ticket,
|
||||
},
|
||||
priority: match summary.workflow_state {
|
||||
TicketWorkflowState::Queued | TicketWorkflowState::InProgress => {
|
||||
ActionPriority::ActiveWork
|
||||
}
|
||||
_ => ActionPriority::Background,
|
||||
},
|
||||
action: Some(NextUserAction::Wait),
|
||||
disabled_reason: Some(format!(
|
||||
"Queue disabled: {waiting_reason}. Resolve dependency/blocker before ready -> queued."
|
||||
)),
|
||||
key_hint: Some(format!("Gate: {waiting_reason}")),
|
||||
blocked_reason: Some(blockers),
|
||||
};
|
||||
}
|
||||
let waiting_reason = format!("waiting for {blockers}");
|
||||
|
||||
let blockers = format_relation_blockers(
|
||||
&relation_blockers
|
||||
.iter()
|
||||
.collect::<Vec<&TicketRelationBlocker>>(),
|
||||
);
|
||||
return DerivedTicketState {
|
||||
kind: match summary.workflow_state {
|
||||
TicketWorkflowState::Planning => PanelRowKind::Planning,
|
||||
TicketWorkflowState::Queued | TicketWorkflowState::InProgress => {
|
||||
PanelRowKind::ActiveWork
|
||||
}
|
||||
TicketWorkflowState::Done | TicketWorkflowState::Closed => PanelRowKind::Review,
|
||||
TicketWorkflowState::Ready => PanelRowKind::Ticket,
|
||||
},
|
||||
priority: match summary.workflow_state {
|
||||
TicketWorkflowState::Queued | TicketWorkflowState::InProgress => {
|
||||
ActionPriority::ActiveWork
|
||||
}
|
||||
_ => ActionPriority::Background,
|
||||
},
|
||||
action: Some(NextUserAction::Wait),
|
||||
disabled_reason: Some(format!(
|
||||
"Queue disabled: {waiting_reason}. Resolve dependency/blocker before ready -> queued."
|
||||
kind: PanelRowKind::Ticket,
|
||||
priority: ActionPriority::ReadyForQueue,
|
||||
action: Some(NextUserAction::Queue),
|
||||
disabled_reason: None,
|
||||
key_hint: Some(format!(
|
||||
"Queue allowed: prerequisites are already queued/in progress; Orchestrator will preserve order ({blockers})."
|
||||
)),
|
||||
key_hint: Some(format!("Gate: {waiting_reason}")),
|
||||
blocked_reason: Some(blockers),
|
||||
blocked_reason: None,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1635,15 +1684,6 @@ mod tests {
|
|||
.unwrap_or_else(|| panic!("missing row for {title}"))
|
||||
}
|
||||
|
||||
fn status_contains(row: &PanelRow, needle: &str) {
|
||||
assert!(
|
||||
row.status.contains(needle),
|
||||
"status {:?} did not contain {:?}",
|
||||
row.status,
|
||||
needle
|
||||
);
|
||||
}
|
||||
|
||||
fn live_pods(names: &[&str]) -> PodList {
|
||||
PodList::from_sources(
|
||||
crate::pod_list::PodVisibilitySource::ResumePicker,
|
||||
|
|
@ -1731,8 +1771,7 @@ mod tests {
|
|||
let model = build_workspace_panel(temp.path(), &empty_pods());
|
||||
|
||||
let matched = ticket_row_by_title(&model, "Overlay Match");
|
||||
status_contains(matched, "local: queued");
|
||||
status_contains(matched, "orchestration: inprogress");
|
||||
assert_eq!(matched.status, "q→prog");
|
||||
assert_eq!(
|
||||
matched.ticket.as_ref().unwrap().workflow_state,
|
||||
TicketWorkflowState::Queued
|
||||
|
|
@ -1772,8 +1811,7 @@ mod tests {
|
|||
let model = build_workspace_panel(temp.path(), &empty_pods());
|
||||
|
||||
let row = ticket_row_by_title(&model, "Overlay In Progress");
|
||||
status_contains(row, "local: queued");
|
||||
status_contains(row, "orchestration: inprogress");
|
||||
assert_eq!(row.status, "q→prog");
|
||||
assert_eq!(row.next_action, Some(NextUserAction::Wait));
|
||||
assert_eq!(row.kind, PanelRowKind::ActiveWork);
|
||||
assert_eq!(fs::read_to_string(&local_item).unwrap(), before);
|
||||
|
|
@ -1801,8 +1839,7 @@ mod tests {
|
|||
let model = build_workspace_panel(temp.path(), &empty_pods());
|
||||
|
||||
let row = ticket_row_by_title(&model, "Overlay Done");
|
||||
status_contains(row, "local: queued");
|
||||
status_contains(row, "orchestration: done");
|
||||
assert_eq!(row.status, "q→done");
|
||||
assert_eq!(row.kind, PanelRowKind::Review);
|
||||
assert_eq!(row.next_action, Some(NextUserAction::Wait));
|
||||
assert_ne!(row.next_action, Some(NextUserAction::Queue));
|
||||
|
|
@ -2123,6 +2160,50 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_panel_allows_ready_ticket_when_relation_prerequisite_is_queued() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
write_ticket_config(temp.path());
|
||||
let backend = LocalTicketBackend::new(temp.path().join(".yoi/tickets"));
|
||||
let mut ready_input = NewTicket::new("Ready After Queued Relation");
|
||||
ready_input.workflow_state = Some(TicketWorkflowState::Ready);
|
||||
let ready = backend.create(ready_input).unwrap();
|
||||
let mut dependency_input = NewTicket::new("Queued Relation Dependency");
|
||||
dependency_input.workflow_state = Some(TicketWorkflowState::Queued);
|
||||
let dependency = backend.create(dependency_input).unwrap();
|
||||
backend
|
||||
.add_ticket_relation(
|
||||
TicketIdOrSlug::Id(ready.id.clone()),
|
||||
NewTicketRelation {
|
||||
kind: TicketRelationKind::DependsOn,
|
||||
target: dependency.id.clone(),
|
||||
note: None,
|
||||
author: Some("test".to_string()),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let model = build_workspace_panel(temp.path(), &empty_pods());
|
||||
let row = model
|
||||
.rows
|
||||
.iter()
|
||||
.find(|row| row.title == "Ready After Queued Relation")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(row.kind, PanelRowKind::Ticket);
|
||||
assert_eq!(row.next_action, Some(NextUserAction::Queue));
|
||||
assert_eq!(row.priority, ActionPriority::ReadyForQueue);
|
||||
assert!(row.disabled_reason.is_none());
|
||||
assert!(row.ticket.as_ref().unwrap().blocked_reason.is_none());
|
||||
assert!(
|
||||
row.key_hint
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.contains("Queue allowed: prerequisites are already queued/in progress")
|
||||
);
|
||||
assert!(row.key_hint.as_deref().unwrap().contains(&dependency.id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_panel_defaults_missing_open_state_to_planning_and_displays_done_state() {
|
||||
let temp = TempDir::new().unwrap();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user