feat(awooop): surface commander inserted work items
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 53s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-07-02 13:35:18 +08:00
parent c909bf6217
commit 4849e3e212
5 changed files with 991 additions and 0 deletions

View File

@@ -9157,6 +9157,30 @@
"evidenceBoundary": "Evidence boundary"
}
},
"commanderInsertedRequirements": {
"eyebrow": "Mainline priority",
"title": "Commander inserted requirement work items",
"subtitle": "Turns the requirements inserted during this run into ordered work items with P0/P1/P2/P3 priority, status, acceptance criteria, and next action.",
"total": "Total work items",
"next": "Current priority",
"nextAction": "Next action",
"acceptance": "Acceptance",
"rowNextAction": "Next action",
"loading": "Loading inserted requirement work items",
"empty": "No inserted requirement work items read back yet.",
"order": "order={order}",
"source": "source={source}",
"metrics": {
"active": "active"
},
"statuses": {
"done": "Done",
"inProgress": "In progress",
"pending": "Not started",
"blocked": "Blocked",
"deferred": "Deferred"
}
},
"operatorSop": {
"eyebrow": "操作 SOP 判讀",
"title": "AI 受控卡點與自動化缺口接手面板",

View File

@@ -9157,6 +9157,30 @@
"evidenceBoundary": "證據邊界"
}
},
"commanderInsertedRequirements": {
"eyebrow": "主線優先序",
"title": "統帥插入需求工作項",
"subtitle": "把本輪中途插入的要求收成正式工作項,依 P0/P1/P2/P3 排序,並顯示狀態、驗收條件與下一步。",
"total": "總工作項",
"next": "目前優先項",
"nextAction": "下一步",
"acceptance": "驗收條件",
"rowNextAction": "下一步",
"loading": "讀取插入需求工作項",
"empty": "尚未讀回插入需求工作項。",
"order": "order={order}",
"source": "source={source}",
"metrics": {
"active": "active"
},
"statuses": {
"done": "已完成",
"inProgress": "進行中",
"pending": "未開始",
"blocked": "阻塞",
"deferred": "延後"
}
},
"operatorSop": {
"eyebrow": "操作 SOP 判讀",
"title": "AI 受控卡點與自動化缺口接手面板",

View File

@@ -1026,8 +1026,38 @@ type AiLoopLogSourceContract = {
purpose?: string | null;
};
type CommanderInsertedRequirementWorkItem = {
id?: string | null;
priority?: "P0" | "P1" | "P2" | "P3" | string | null;
order?: number | null;
status?: "done" | "in_progress" | "pending" | "blocked" | "deferred" | string | null;
lane?: string | null;
request?: string | null;
normalized_work_item?: string | null;
current_state?: string | null;
acceptance?: string | null;
next_action?: string | null;
mapped_workplan_id?: string | null;
visible_surface?: string | null;
source?: string | null;
};
type PriorityWorkOrderResponse = {
summary?: {
commander_inserted_requirement_work_item_count?: number | null;
commander_inserted_requirement_p0_count?: number | null;
commander_inserted_requirement_p1_count?: number | null;
commander_inserted_requirement_p2_count?: number | null;
commander_inserted_requirement_p3_count?: number | null;
commander_inserted_requirement_done_count?: number | null;
commander_inserted_requirement_in_progress_count?: number | null;
commander_inserted_requirement_pending_count?: number | null;
commander_inserted_requirement_blocked_count?: number | null;
commander_inserted_requirement_deferred_count?: number | null;
commander_inserted_requirement_next_id?: string | null;
commander_inserted_requirement_next_priority?: string | null;
commander_inserted_requirement_next_action?: string | null;
commander_inserted_requirement_source?: string | null;
ai_loop_current_blocker_id?: string | null;
ai_loop_current_blocker_log_source_tag_count?: number | null;
ai_loop_current_blocker_log_source_tag_keys?: string[] | null;
@@ -1102,6 +1132,7 @@ type PriorityWorkOrderResponse = {
controlled_cd_lane_live_metric_blockers?: string[] | null;
} | null;
}>;
commander_inserted_requirement_work_items?: CommanderInsertedRequirementWorkItem[];
};
type Telemetry = {
@@ -8400,6 +8431,235 @@ function AiLoopLogSourceTagsPanel({
);
}
function commanderPriorityTone(priority: string) {
switch (priority) {
case "P0":
return "border-[#f0c6a8] bg-[#fff8f1] text-[#9a4d16]";
case "P1":
return "border-[#c9d8ea] bg-[#eef5ff] text-[#1f5b9b]";
case "P2":
return "border-[#cbd7bf] bg-[#f4faef] text-[#3d6b24]";
default:
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
}
}
function commanderStatusTone(status: string) {
switch (status) {
case "done":
return "border-[#b9d9c2] bg-[#f2fbf3] text-[#236332]";
case "in_progress":
return "border-[#c9d8ea] bg-[#eef5ff] text-[#1f5b9b]";
case "blocked":
return "border-[#f0c6a8] bg-[#fff8f1] text-[#9a4d16]";
case "deferred":
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
default:
return "border-[#e0ddd4] bg-white text-[#5f5b52]";
}
}
function CommanderInsertedRequirementsPanel({
priority,
loading,
}: {
priority: PriorityWorkOrderResponse | null;
loading: boolean;
}) {
const t = useTranslations("awooop.workItems.commanderInsertedRequirements");
const summary = priority?.summary;
const items = (priority?.commander_inserted_requirement_work_items ?? [])
.slice()
.sort((left, right) => (left.order ?? 0) - (right.order ?? 0));
const total =
summary?.commander_inserted_requirement_work_item_count ?? items.length;
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 source = summary?.commander_inserted_requirement_source ?? items[0]?.source ?? "--";
const metrics = [
{
key: "p0",
label: "P0",
value: summary?.commander_inserted_requirement_p0_count ?? 0,
tone: commanderPriorityTone("P0"),
},
{
key: "p1",
label: "P1",
value: summary?.commander_inserted_requirement_p1_count ?? 0,
tone: commanderPriorityTone("P1"),
},
{
key: "p2",
label: "P2",
value: summary?.commander_inserted_requirement_p2_count ?? 0,
tone: commanderPriorityTone("P2"),
},
{
key: "status",
label: t("metrics.active"),
value:
(summary?.commander_inserted_requirement_in_progress_count ?? 0) +
(summary?.commander_inserted_requirement_pending_count ?? 0) +
(summary?.commander_inserted_requirement_blocked_count ?? 0),
tone: "border-[#e0ddd4] bg-white text-[#141413]",
},
];
const statusLabels: Record<string, string> = {
done: t("statuses.done"),
in_progress: t("statuses.inProgress"),
pending: t("statuses.pending"),
blocked: t("statuses.blocked"),
deferred: t("statuses.deferred"),
};
return (
<section
className="border border-[#e0ddd4] bg-white"
data-testid="commander-inserted-requirement-work-items"
>
<div className="p-4">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="flex min-w-0 items-center gap-3">
<ListChecks className="h-5 w-5 shrink-0 text-[#d97757]" aria-hidden="true" />
<div className="min-w-0">
<p className="text-[11px] font-semibold uppercase tracking-[0.08em] text-[#9a968c]">
{t("eyebrow")}
</p>
<h3 className="mt-1 text-base font-semibold tracking-normal text-[#141413]">
{t("title")}
</h3>
<p className="mt-2 max-w-3xl text-xs leading-5 text-[#77736a]">
{t("subtitle")}
</p>
</div>
</div>
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-2 text-right">
<div className="text-[11px] font-semibold text-[#77736a]">
{t("total")}
</div>
<div className="mt-1 font-mono text-xl font-semibold text-[#141413]">
{loading ? "--" : total}
</div>
</div>
</div>
<div className="mt-4 grid gap-2 sm:grid-cols-2 xl:grid-cols-4">
{metrics.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-3 grid gap-2 border border-[#c9d8ea] bg-[#eef5ff] p-3 md:grid-cols-[minmax(0,0.85fr)_minmax(0,2fr)]">
<div className="min-w-0">
<div className="text-[11px] font-semibold text-[#1f5b9b]">
{t("next")}
</div>
<div className="mt-1 break-words font-mono text-sm font-semibold text-[#141413]">
{loading ? "--" : `${nextPriority}:${nextId}`}
</div>
</div>
<div className="min-w-0">
<div className="text-[11px] font-semibold text-[#1f5b9b]">
{t("nextAction")}
</div>
<div className="mt-1 break-words text-xs leading-5 text-[#141413]">
{loading ? "--" : nextAction}
</div>
</div>
</div>
<div className="mt-4 grid gap-2">
{items.length > 0 ? (
items.map((item) => {
const itemPriority = String(item.priority ?? "--");
const itemStatus = String(item.status ?? "pending");
return (
<div
key={item.id ?? item.order}
className="grid gap-3 border border-[#e0ddd4] bg-[#faf9f3] p-3 lg:grid-cols-[minmax(112px,0.45fr)_minmax(0,1.6fr)_minmax(0,1.1fr)]"
>
<div className="min-w-0">
<div className="flex flex-wrap 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)
)}
>
{statusLabels[itemStatus] ?? itemStatus}
</span>
</div>
<div className="mt-2 break-all font-mono text-xs font-semibold text-[#141413]">
{loading ? "--" : item.id}
</div>
<div className="mt-1 font-mono text-[10px] text-[#77736a]">
{t("order", { order: item.order ?? 0 })}
</div>
</div>
<div className="min-w-0">
<div className="break-words text-xs font-semibold leading-5 text-[#141413]">
{loading ? "--" : item.normalized_work_item}
</div>
<div className="mt-2 break-words text-[11px] leading-5 text-[#77736a]">
{loading ? "--" : item.request}
</div>
<div className="mt-2 break-words font-mono text-[10px] leading-4 text-[#5f5b52]">
{loading ? "--" : item.mapped_workplan_id}
</div>
</div>
<div className="min-w-0 border border-[#d8d3c7] bg-white px-3 py-2">
<div className="text-[11px] font-semibold text-[#77736a]">
{t("acceptance")}
</div>
<div className="mt-1 break-words text-[11px] leading-5 text-[#141413]">
{loading ? "--" : item.acceptance}
</div>
<div className="mt-2 text-[11px] font-semibold text-[#77736a]">
{t("rowNextAction")}
</div>
<div className="mt-1 break-words text-[11px] leading-5 text-[#141413]">
{loading ? "--" : item.next_action}
</div>
</div>
</div>
);
})
) : (
<div className="border border-[#d8d3c7] bg-[#faf9f3] px-3 py-6 text-center text-xs text-[#77736a]">
{loading ? t("loading") : t("empty")}
</div>
)}
</div>
<div className="mt-3 break-words font-mono text-[10px] leading-4 text-[#9a968c]">
{t("source", { source })}
</div>
</div>
</section>
);
}
export default function AwoooPWorkItemsPage() {
const t = useTranslations("awooop.workItems");
const locale = useLocale();
@@ -8687,6 +8947,11 @@ export default function AwoooPWorkItemsPage() {
loading={loading && !telemetry.priorityWorkOrder}
/>
<CommanderInsertedRequirementsPanel
priority={telemetry.priorityWorkOrder}
loading={loading && !telemetry.priorityWorkOrder}
/>
<OperatorSopRail
telemetry={telemetry}
workItems={workItems}