feat(web): add homepage command swimlanes
This commit is contained in:
@@ -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))',
|
||||
|
||||
Reference in New Issue
Block a user