feat: refine workbench visuals and map controls

Unify the Agent history extension with the header acrylic surface, preserve the full conversation body, and consolidate shared control and status styling.

Restore map flow and SCADA controller behavior, remove obsolete rendering paths, and extend regression coverage. Button press coverage now releases outside the target so state assertions cannot accidentally toggle the control.
This commit is contained in:
2026-07-28 16:39:36 +08:00
parent ade88b4fbf
commit f4318a9b9c
96 changed files with 5480 additions and 1875 deletions
@@ -21,6 +21,7 @@ import {
DropdownMenuRadioItem,
DropdownMenuTrigger
} from "@/shared/ui/dropdown-menu";
import { StatusIcon as SemanticStatusIcon } from "@/shared/ui/status";
import { ConditionDetailPanel, StatusPill } from "./scheduled-condition-detail-panel";
import {
CONDITION_FILTER_ALL,
@@ -28,8 +29,12 @@ import {
createTaskFilterOptions,
formatTime,
getConditionTime,
getStatusActivity,
getStatusIcon,
getStatusIconClassName,
getStatusTone,
getWorkOrderCurrentStage,
getWorkOrderStageActivity,
getWorkOrderStageTone,
groupScheduledConditions,
type ConditionStatusFilterOption,
type ConditionStatusFilterValue,
@@ -286,7 +291,12 @@ export function ScheduledConditionFeed({
MAP_TOOL_PANEL_SURFACE_CLASS_NAME,
"max-w-[calc(100vw-7rem)]",
desktopFloating
? "max-h-[calc(100dvh-8rem)] rounded-2xl"
? cn(
"rounded-2xl",
expanded
? "h-[calc(100dvh-8rem)]"
: "max-h-[calc(100dvh-8rem)]"
)
: "h-full rounded-l-2xl rounded-r-none",
expanded
? "scheduled-feed-panel-expanded flex flex-col"
@@ -334,11 +344,9 @@ export function ScheduledConditionFeed({
aria-label="关闭工况任务"
title="关闭工况任务"
onClick={onClose}
className="group grid h-10 w-10 shrink-0 place-items-center rounded-full text-slate-500"
className="agent-panel-icon-button grid h-10 w-10 shrink-0 place-items-center rounded-xl text-slate-600"
>
<span className="surface-control grid h-9 w-9 place-items-center rounded-full shadow-[inset_0_0_0_1px_rgba(148,163,184,0.26),0_4px_12px_rgba(15,33,55,0.10)] transition-[color,background-color,scale] group-active:scale-95 [@media(hover:hover)]:group-hover:bg-blue-50 [@media(hover:hover)]:group-hover:text-blue-700">
<X size={17} strokeWidth={2.25} aria-hidden="true" />
</span>
<X size={17} aria-hidden="true" />
</button>
) : null}
</header>
@@ -349,19 +357,19 @@ export function ScheduledConditionFeed({
mobileSheet
? "flex flex-1"
: expanded
? "scheduled-feed-layout-expanded"
? "scheduled-feed-layout-expanded flex-1"
: "scheduled-feed-layout-collapsed"
)}
>
<div
className={cn(
"min-h-0 min-w-0 overflow-hidden",
"relative min-h-0 min-w-0 overflow-hidden",
mobileSheet
? expanded
? "scheduled-feed-detail-enter h-full w-full"
: "hidden"
: expanded
? "scheduled-feed-detail-enter"
? "scheduled-feed-detail-enter h-full"
: "pointer-events-none opacity-0"
)}
aria-hidden={!expanded}
@@ -459,7 +467,7 @@ function ConditionTimelinePanel({
presentation === "mobile-sheet"
? "h-full max-h-none"
: expanded
? "h-[calc(100dvh-8.75rem)] max-h-[calc(100dvh-8.75rem)]"
? "h-full max-h-none"
: "h-[420px] max-h-[420px]",
CONDITION_CONTENT_SURFACE_CLASS_NAME
)}
@@ -486,7 +494,7 @@ function ConditionTimelinePanel({
count={futureWorkOrders.length}
compact
/>
<div className="scheduled-feed-scroll max-h-[132px] overflow-y-auto pr-1">
<div className="scheduled-feed-scroll -mr-2 max-h-[132px] overflow-y-auto pl-0.5 pr-2 pt-0.5">
<ConditionRows
conditions={futureWorkOrders}
selectedConditionId={selectedConditionId}
@@ -583,7 +591,7 @@ function ConditionFilterBar({
<button
type="button"
onClick={onResetFilters}
className="surface-control mt-2 inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-lg border border-slate-200/70 px-2 text-xs font-semibold text-blue-700 transition-[background-color,transform] hover:bg-blue-50 active:scale-95"
className="surface-control relative mt-2 inline-flex h-8 w-full items-center justify-center gap-1.5 rounded-lg border border-slate-200/70 px-2 text-xs font-semibold text-blue-700 after:absolute after:-inset-1 after:content-[''] hover:bg-blue-50"
>
<XCircle size={12} aria-hidden="true" />
@@ -722,10 +730,7 @@ function ConditionDetailSkeleton() {
return (
<DetailScroll>
<div
className={cn(
"surface-reading relative min-h-[420px] overflow-hidden border border-slate-200/70 px-4 py-3",
"rounded-xl"
)}
className="relative min-h-[420px] overflow-hidden px-4 py-3"
role="status"
aria-label="工况详情加载中"
>
@@ -760,8 +765,18 @@ function ConditionDetailSkeleton() {
function DetailScroll({ children }: { children: React.ReactNode }) {
return (
<section className="scheduled-feed-scroll scheduled-feed-detail-scroll max-h-[calc(100dvh-14rem)] min-h-0 overflow-y-scroll py-0.5 pl-0.5 pr-3">
{children}
<section
className={cn(
CONDITION_CONTENT_SURFACE_CLASS_NAME,
"scheduled-feed-detail-viewport absolute inset-0 overflow-hidden"
)}
>
<div
data-testid="scheduled-condition-detail-scroll"
className="scheduled-feed-scroll absolute inset-x-0 inset-y-2 overflow-y-auto pb-4 pl-0.5 pr-2 pt-0.5"
>
{children}
</div>
</section>
);
}
@@ -830,8 +845,15 @@ function ConditionTimelineRow({
isLast,
onSelect
}: ConditionTimelineRowProps) {
const StatusIcon = getStatusIcon(condition.status);
const Icon = getStatusIcon(condition.status);
const isWorkOrder = condition.kind === "work_order";
const workOrderStage = isWorkOrder ? getWorkOrderCurrentStage(condition)?.id : undefined;
const tone = isWorkOrder
? getWorkOrderStageTone(workOrderStage)
: getStatusTone(condition.status);
const activity = isWorkOrder
? getWorkOrderStageActivity(workOrderStage)
: getStatusActivity(condition.status);
return (
<button
@@ -855,22 +877,18 @@ function ConditionTimelineRow({
aria-hidden="true"
/>
) : null}
<span
<SemanticStatusIcon
tone={tone}
activity={activity}
shape="circle"
data-selected={selected ? "true" : undefined}
className={cn(
"relative z-10 grid h-6 w-6 place-items-center rounded-full ring-4 ring-white",
selected
? "bg-blue-600 text-white group-focus-visible:ring-blue-100"
: isWorkOrder
? "bg-blue-50 text-blue-700 group-focus-visible:bg-blue-100 group-focus-visible:text-blue-800"
: getStatusIconClassName(condition.status)
"relative z-10 ring-4 ring-white",
selected && "group-focus-visible:ring-blue-100"
)}
>
<StatusIcon
size={12}
aria-hidden="true"
className={condition.status === "running" ? "animate-spin" : undefined}
/>
</span>
<Icon size={12} />
</SemanticStatusIcon>
</span>
<span
className={cn(