feat(web): add homepage command swimlanes
All checks were successful
CD Pipeline / tests (push) Successful in 1m20s
Code Review / ai-code-review (push) Successful in 12s
CD Pipeline / build-and-deploy (push) Successful in 3m23s
CD Pipeline / post-deploy-checks (push) Successful in 1m28s

This commit is contained in:
Your Name
2026-06-04 10:54:30 +08:00
parent 8b76827d33
commit 709c071a9e
4 changed files with 225 additions and 0 deletions

View File

@@ -1879,6 +1879,55 @@ export default function Home({ params }: { params: { locale: string } }) {
}),
},
]
const commandMapSwimlanes: Array<{
key: string
label: string
summary: string
tone: HomepageWorkTone
stages: HomepageBlueprintStage[]
}> = [
{
key: 'signals',
label: tDashboard('homeCommandMap.swimlanes.signals'),
summary: tDashboard('automationDiagrams.workspace.liveEvidence.signal.metric', {
sources: formatAutomationNumber(dossierCoverageSummary?.source_count, dossierCoverageLoaded),
refs: formatAutomationNumber(dossierCoverageSummary?.source_ref_total, dossierCoverageLoaded),
}),
tone: dossierCoverageLoaded ? 'live' : 'watching',
stages: automationFlowStages.filter(stage => stage.key === 'signal' || stage.key === 'intake'),
},
{
key: 'reasoning',
label: tDashboard('homeCommandMap.swimlanes.reasoning'),
summary: tDashboard('automationDiagrams.workspace.values.aiRoute', {
lane: aiRouteLaneMode,
provider: aiRouteSelectedProvider,
}),
tone: hasAiRouteStatus ? 'live' : 'watching',
stages: automationFlowStages.filter(stage => stage.key === 'ai' || stage.key === 'mcp'),
},
{
key: 'execution',
label: tDashboard('homeCommandMap.swimlanes.execution'),
summary: tDashboard('automationDiagrams.workspace.values.ansible', {
checkMode: formatAutomationNumber(executionBackend?.ansible_check_mode_total, automationQualityLoaded),
pending: formatAutomationNumber(executionBackend?.ansible_pending_check_mode_total, automationQualityLoaded),
blocker: ansibleRuntimeBlockerLabel,
}),
tone: ansibleRuntime?.can_run_check_mode ? 'live' : automationQualityAvailable ? 'blocked' : 'watching',
stages: automationFlowStages.filter(stage => stage.key === 'playbook' || stage.key === 'ansible'),
},
{
key: 'governance',
label: tDashboard('homeCommandMap.swimlanes.governance'),
summary: tDashboard('automationDiagrams.workspace.liveEvidence.verify.metric', {
stale: formatAutomationNumber(kmStaleDisplayCount, kmGovernanceLoaded),
ratio: staleRatioLabel,
}),
tone: typeof kmStaleDisplayCount === 'number' && kmStaleDisplayCount > 0 ? 'progress' : hasKmStaleCandidates ? 'live' : 'watching',
stages: automationFlowStages.filter(stage => stage.key === 'approval' || stage.key === 'verify'),
},
]
const productModuleRows: Array<{
key: string
module: string
@@ -2263,6 +2312,139 @@ export default function Home({ params }: { params: { locale: string } }) {
})}
</div>
<div style={{
display: 'grid',
gridTemplateColumns: compactViewport ? '1fr' : 'repeat(4, minmax(0, 1fr))',
gap: 8,
marginBottom: 10,
}}>
{commandMapSwimlanes.map((lane) => {
const laneTone = automationWorkToneStyle[lane.tone]
return (
<div
key={lane.key}
style={{
minWidth: 0,
border: `0.5px solid ${laneTone.border}`,
borderRadius: 9,
background: '#fffdf8',
overflow: 'hidden',
}}
>
<div style={{
borderBottom: `0.5px solid ${laneTone.border}`,
background: laneTone.bg,
padding: '7px 8px',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
<span style={{
width: 8,
height: 8,
borderRadius: 999,
background: laneTone.color,
flexShrink: 0,
}} />
<div style={{
minWidth: 0,
fontSize: 10,
fontWeight: 900,
color: laneTone.color,
textTransform: 'uppercase',
letterSpacing: 0.35,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{lane.label}
</div>
</div>
<div style={{
marginTop: 5,
minHeight: 30,
fontSize: 10,
lineHeight: 1.45,
color: '#4e4a43',
overflowWrap: 'anywhere',
}}>
{lane.summary}
</div>
</div>
<div style={{
display: 'grid',
gridTemplateColumns: '1fr',
gap: 6,
padding: 8,
}}>
{lane.stages.map((stage, index) => {
const stageTone = automationWorkToneStyle[stage.tone]
const isSelectedStage = stage.key === selectedBlueprintStage.key
return (
<a
key={`${lane.key}-${stage.key}`}
href={`/${locale}?blueprint_stage=${stage.key}#homepage-ai-command-map`}
onClick={() => setSelectedBlueprintStageKey(stage.key as HomepageBlueprintStageKey)}
aria-current={isSelectedStage ? 'step' : undefined}
style={{
position: 'relative',
display: 'grid',
gridTemplateColumns: '22px minmax(0, 1fr)',
gap: 7,
alignItems: 'center',
minHeight: 34,
border: `0.5px solid ${isSelectedStage ? stageTone.color : '#e0ddd4'}`,
borderRadius: 7,
background: isSelectedStage ? stageTone.bg : '#fff',
color: 'inherit',
textDecoration: 'none',
padding: '5px 6px',
}}
>
{index > 0 && (
<span style={{
position: 'absolute',
left: 16,
top: -7,
width: 1,
height: 7,
background: '#c8c0b2',
}} />
)}
<span style={{
width: 22,
height: 22,
borderRadius: 999,
border: `0.5px solid ${stageTone.border}`,
background: stageTone.bg,
color: stageTone.color,
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 9,
fontWeight: 900,
fontFamily: "'JetBrains Mono', monospace",
}}>
{automationFlowStages.findIndex(flowStage => flowStage.key === stage.key) + 1}
</span>
<span style={{
minWidth: 0,
fontSize: 10,
fontWeight: 850,
color: '#141413',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{stage.title}
</span>
</a>
)
})}
</div>
</div>
)
})}
</div>
<div style={{
display: 'grid',
gridTemplateColumns: compactViewport ? '1fr' : 'repeat(4, minmax(0, 1fr))',