fix(work-items): surface mainline progress order
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 1m9s
CD Pipeline / build-and-deploy (push) Successful in 8m17s
CD Pipeline / post-deploy-checks (push) Successful in 2m46s

This commit is contained in:
ogt
2026-07-10 09:49:22 +08:00
parent 771176c797
commit b8e987bde4
3 changed files with 270 additions and 18 deletions

View File

@@ -9531,31 +9531,49 @@
"noSecret": "no secret read"
}
},
"mainlineProgress": {
"eyebrow": "Mainline Work Progress",
"title": "All Work Items And Priority Order",
"subtitle": "Reads the production priority work-order readback and surfaces totals, completion, current P0, next action, and the first 8 ordered items before the deeper automation / ledger views.",
"loading": "Reading production work-order",
"completion": "Overall completion",
"currentP0": "Current P0",
"primaryBlocker": "Primary blocker",
"blockerCount": "{count} blockers",
"nextAction": "Next ordered action",
"statuses": {
"done": "Completed",
"in_progress": "In progress",
"pending": "Not started",
"blocked": "Blocked",
"deferred": "Deferred"
}
},
"commanderInsertedRequirements": {
"eyebrow": "主線優先序",
"title": "統帥插入需求工作項",
"subtitle": "把本輪中途插入的要求收成正式工作項,依 P0/P1/P2/P3 排序,並顯示狀態、驗收條件與下一步。",
"total": "總工作項",
"next": "目前優先項",
"nextAction": "下一步",
"acceptance": "驗收條件",
"rowNextAction": "下一步",
"spotlight": "主線焦點",
"spotlightSubtitle": "直接露出目前 P0OpenClaw Live Ops 與非文字牆 UX避免工作項藏在長清單裡。",
"fullLedger": "完整總帳 {count} ",
"loading": "讀取插入需求工作項",
"empty": "尚未讀回插入需求工作項。",
"eyebrow": "Mainline priority",
"title": "Commander Inserted Requirement Work Items",
"subtitle": "Turns midstream requirements into ordered P0/P1/P2/P3 work items with status, acceptance, and next action.",
"total": "Total work items",
"next": "Current priority",
"nextAction": "Next action",
"acceptance": "Acceptance",
"rowNextAction": "Next action",
"spotlight": "Mainline spotlight",
"spotlightSubtitle": "Pins current P0, OpenClaw Live Ops, and UI work so they do not hide in a long ledger.",
"fullLedger": "Full ledger {count} items",
"loading": "Reading inserted requirement work items",
"empty": "No inserted requirement work items read back yet.",
"order": "order={order}",
"source": "source={source}",
"metrics": {
"active": "active"
},
"statuses": {
"done": "已完成",
"inProgress": "進行中",
"pending": "未開始",
"blocked": "阻塞",
"deferred": "延後"
"done": "Completed",
"inProgress": "In progress",
"pending": "Not started",
"blocked": "Blocked",
"deferred": "Deferred"
}
},
"operatorSop": {

View File

@@ -9531,6 +9531,24 @@
"noSecret": "no secret read"
}
},
"mainlineProgress": {
"eyebrow": "主線工作進度",
"title": "全部工作項目與優先順序",
"subtitle": "直接讀正式 priority work-order readback先看總數、完成度、目前 P0、下一步與前 8 個 ordered items細節再下鑽 automation / ledger。",
"loading": "讀取正式 work-order",
"completion": "整體完成度",
"currentP0": "目前 P0",
"primaryBlocker": "主要阻擋",
"blockerCount": "{count} 個 blocker",
"nextAction": "下一個 ordered action",
"statuses": {
"done": "已完成",
"in_progress": "進行中",
"pending": "未開始",
"blocked": "阻塞",
"deferred": "延後"
}
},
"commanderInsertedRequirements": {
"eyebrow": "主線優先序",
"title": "統帥插入需求工作項",

View File

@@ -1091,6 +1091,7 @@ type PriorityWorkOrderResponse = {
active_p0_state?: string | null;
active_p0_workplan_id?: string | null;
active_p0_readiness_percent?: number | null;
active_p0_primary_blocker?: string | null;
active_p0_live_active_blockers?: string[] | null;
next_executable_mainline_workplan_id?: string | null;
next_executable_mainline_state?: string | null;
@@ -11065,6 +11066,216 @@ function commanderStatusTone(status: string) {
}
}
function MainlineWorkProgressStrip({
priority,
loading,
}: {
priority: PriorityWorkOrderResponse | null;
loading: boolean;
}) {
const t = useTranslations("awooop.workItems.mainlineProgress");
const summary = priority?.summary;
const items = (priority?.commander_inserted_requirement_work_items ?? [])
.slice()
.sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
const total = toCount(
summary?.commander_inserted_requirement_work_item_count ?? items.length
);
const done = toCount(summary?.commander_inserted_requirement_done_count);
const inProgress = toCount(
summary?.commander_inserted_requirement_in_progress_count
);
const pending = toCount(summary?.commander_inserted_requirement_pending_count);
const blocked = toCount(summary?.commander_inserted_requirement_blocked_count);
const deferred = toCount(summary?.commander_inserted_requirement_deferred_count);
const completion = total > 0 ? Math.round((done / total) * 100) : 0;
const readiness = toCount(summary?.active_p0_readiness_percent);
const nextId =
summary?.commander_inserted_requirement_next_id ?? items[0]?.id ?? "--";
const nextPriority =
summary?.commander_inserted_requirement_next_priority ??
items[0]?.priority ??
"--";
const nextAction =
summary?.commander_inserted_requirement_next_action ??
items[0]?.next_action ??
"--";
const currentP0 = summary?.active_p0_workplan_id ?? "--";
const currentState = summary?.active_p0_state ?? "--";
const activeBlockers = summary?.active_p0_live_active_blockers ?? [];
const primaryBlocker =
summary?.active_p0_primary_blocker ?? activeBlockers[0] ?? "--";
const visibleItems = items.slice(0, 8);
const statusMetrics = [
{
key: "done",
label: t("statuses.done"),
value: done,
tone: commanderStatusTone("done"),
},
{
key: "inProgress",
label: t("statuses.in_progress"),
value: inProgress,
tone: commanderStatusTone("in_progress"),
},
{
key: "pending",
label: t("statuses.pending"),
value: pending,
tone: commanderStatusTone("pending"),
},
{
key: "blocked",
label: t("statuses.blocked"),
value: blocked,
tone: commanderStatusTone("blocked"),
},
{
key: "deferred",
label: t("statuses.deferred"),
value: deferred,
tone: commanderStatusTone("deferred"),
},
];
return (
<section
className="border border-[#e0ddd4] bg-white"
data-testid="mainline-work-progress-strip"
>
<div className="grid gap-px bg-[#e0ddd4] lg:grid-cols-[minmax(0,1.2fr)_minmax(280px,0.8fr)]">
<div className="bg-white p-4">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<span className="inline-flex items-center gap-2 border border-[#c9d8ea] bg-[#eef5ff] px-2 py-0.5 text-xs font-semibold text-[#1f5b9b]">
<ListChecks className="h-3.5 w-3.5" aria-hidden="true" />
{t("eyebrow")}
</span>
<span className="font-mono text-xs text-[#77736a]">
{loading ? t("loading") : `${nextPriority}:${nextId}`}
</span>
</div>
<h3 className="mt-2 text-base font-semibold tracking-normal text-[#141413]">
{t("title")}
</h3>
<p className="mt-1 max-w-4xl text-xs leading-5 text-[#5f5b52]">
{t("subtitle")}
</p>
</div>
<div className="min-w-[220px]">
<div className="flex items-center justify-between gap-3 text-xs font-semibold text-[#5f5b52]">
<span>{t("completion")}</span>
<span className="font-mono text-[#141413]">
{loading ? "--" : `${completion}% · ${done}/${total}`}
</span>
</div>
<div className="mt-2 h-2 border border-[#d8d3c7] bg-[#faf9f3]">
<div
className="h-full bg-[#17602a]"
style={{ width: loading ? "0%" : `${Math.min(100, completion)}%` }}
/>
</div>
</div>
</div>
<div className="mt-4 grid gap-2 sm:grid-cols-2 xl:grid-cols-5">
{statusMetrics.map((metric) => (
<div key={metric.key} className={cn("border px-3 py-2", metric.tone)}>
<div className="text-[11px] font-semibold">{metric.label}</div>
<div className="mt-1 font-mono text-lg font-semibold text-[#141413]">
{loading ? "--" : metric.value}
</div>
</div>
))}
</div>
<div className="mt-4 grid gap-2 xl:grid-cols-4">
{visibleItems.map((item) => {
const itemPriority = String(item.priority ?? "--");
const itemStatus = String(item.status ?? "pending");
return (
<div
key={item.id ?? item.order}
className="min-w-0 border border-[#e0ddd4] bg-[#faf9f3] p-3"
>
<div className="flex flex-wrap items-center gap-1.5">
<span
className={cn(
"border px-2 py-0.5 font-mono text-[11px] font-semibold",
commanderPriorityTone(itemPriority)
)}
>
{itemPriority}
</span>
<span
className={cn(
"border px-2 py-0.5 text-[11px] font-semibold",
commanderStatusTone(itemStatus)
)}
>
{t(`statuses.${itemStatus}` as never)}
</span>
</div>
<div className="mt-2 truncate font-mono text-xs font-semibold text-[#141413]">
{item.id}
</div>
<div className="mt-1 line-clamp-2 text-xs leading-5 text-[#5f5b52]">
{item.normalized_work_item ?? item.request}
</div>
</div>
);
})}
</div>
</div>
<div className="bg-[#faf9f3] p-4">
<div className="grid gap-3">
<div className="border border-[#d8d3c7] bg-white p-3">
<div className="flex items-center justify-between gap-3">
<div className="text-xs font-semibold text-[#77736a]">
{t("currentP0")}
</div>
<span className="font-mono text-xs font-semibold text-[#141413]">
{loading ? "--" : currentP0}
</span>
</div>
<div className="mt-2 font-mono text-xl font-semibold text-[#141413]">
{loading ? "--" : `${readiness}%`}
</div>
<div className="mt-1 line-clamp-2 text-xs leading-5 text-[#5f5b52]">
{currentState}
</div>
</div>
<div className="border border-[#d8d3c7] bg-white p-3">
<div className="text-xs font-semibold text-[#77736a]">
{t("primaryBlocker")}
</div>
<div className="mt-2 line-clamp-3 break-words font-mono text-xs leading-5 text-[#141413]">
{loading ? "--" : primaryBlocker}
</div>
<div className="mt-2 text-xs leading-5 text-[#5f5b52]">
{t("blockerCount", { count: activeBlockers.length })}
</div>
</div>
<div className="border border-[#c9d8ea] bg-[#eef5ff] p-3">
<div className="text-xs font-semibold text-[#1f5b9b]">
{t("nextAction")}
</div>
<div className="mt-2 line-clamp-4 break-words text-xs font-semibold leading-5 text-[#141413]">
{loading ? "--" : nextAction}
</div>
</div>
</div>
</div>
</div>
</section>
);
}
function CommanderInsertedRequirementsPanel({
priority,
loading,
@@ -11775,6 +11986,11 @@ export default function AwoooPWorkItemsPage() {
</div>
</section>
<MainlineWorkProgressStrip
priority={commanderInsertedRequirementWorkOrder}
loading={priorityWorkOrderLoading && !priorityWorkOrder}
/>
<SecurityMainlineCockpit
priority={commanderInsertedRequirementWorkOrder}
locale={locale}