139 lines
3.4 KiB
JavaScript
139 lines
3.4 KiB
JavaScript
/**
|
|
* Flow Ribbon Component - "Ask → Preview → Run → Verify → Done"
|
|
*
|
|
* NOOB-PROOF: Always shows current phase and what to do next
|
|
*
|
|
* Credit: OpenCode-inspired phase ribbon
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { Box, Text } from 'ink';
|
|
import { colors } from '../../tui-theme.mjs';
|
|
import { getCapabilities } from '../../terminal-profile.mjs';
|
|
|
|
const h = React.createElement;
|
|
|
|
// Flow phases
|
|
export const FLOW_PHASES = {
|
|
ASK: 'ask',
|
|
PREVIEW: 'preview',
|
|
RUN: 'run',
|
|
VERIFY: 'verify',
|
|
DONE: 'done'
|
|
};
|
|
|
|
// Phase display config
|
|
const PHASE_CONFIG = {
|
|
[FLOW_PHASES.ASK]: {
|
|
label: 'Ask',
|
|
hint: 'Describe what you want to do',
|
|
icon: '?'
|
|
},
|
|
[FLOW_PHASES.PREVIEW]: {
|
|
label: 'Preview',
|
|
hint: 'Review planned actions — Enter to run, or edit',
|
|
icon: '⊙'
|
|
},
|
|
[FLOW_PHASES.RUN]: {
|
|
label: 'Run',
|
|
hint: 'Executing actions...',
|
|
icon: '▶'
|
|
},
|
|
[FLOW_PHASES.VERIFY]: {
|
|
label: 'Verify',
|
|
hint: 'Checking results...',
|
|
icon: '✓?'
|
|
},
|
|
[FLOW_PHASES.DONE]: {
|
|
label: 'Done',
|
|
hint: 'Task completed',
|
|
icon: '✓'
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Single phase pill
|
|
*/
|
|
const PhasePill = ({ phase, isActive, isPast, isFuture, useAscii }) => {
|
|
const config = PHASE_CONFIG[phase];
|
|
|
|
let color = colors.muted;
|
|
let dimColor = true;
|
|
|
|
if (isActive) {
|
|
color = colors.accent;
|
|
dimColor = false;
|
|
} else if (isPast) {
|
|
color = colors.success;
|
|
dimColor = true;
|
|
}
|
|
|
|
const icon = useAscii
|
|
? (isActive ? '*' : isPast ? '+' : '-')
|
|
: config.icon;
|
|
|
|
return h(Box, { flexDirection: 'row' },
|
|
h(Text, { color, dimColor, bold: isActive },
|
|
`${icon} ${config.label}`
|
|
)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Flow Ribbon - Shows current phase in workflow
|
|
*
|
|
* Props:
|
|
* - currentPhase: one of FLOW_PHASES
|
|
* - showHint: whether to show "what to do next" hint
|
|
* - width: ribbon width
|
|
*/
|
|
const FlowRibbon = ({
|
|
currentPhase = FLOW_PHASES.ASK,
|
|
showHint = true,
|
|
width = 80
|
|
}) => {
|
|
const caps = getCapabilities();
|
|
const phases = Object.values(FLOW_PHASES);
|
|
const currentIndex = phases.indexOf(currentPhase);
|
|
|
|
const separator = caps.unicodeOK ? ' → ' : ' > ';
|
|
const config = PHASE_CONFIG[currentPhase];
|
|
|
|
return h(Box, {
|
|
flexDirection: 'column',
|
|
width: width
|
|
},
|
|
// Phase pills
|
|
h(Box, { flexDirection: 'row' },
|
|
...phases.map((phase, i) => {
|
|
const isActive = phase === currentPhase;
|
|
const isPast = i < currentIndex;
|
|
const isFuture = i > currentIndex;
|
|
|
|
return h(Box, { key: phase, flexDirection: 'row' },
|
|
h(PhasePill, {
|
|
phase,
|
|
isActive,
|
|
isPast,
|
|
isFuture,
|
|
useAscii: !caps.unicodeOK
|
|
}),
|
|
i < phases.length - 1
|
|
? h(Text, { color: colors.muted, dimColor: true }, separator)
|
|
: null
|
|
);
|
|
})
|
|
),
|
|
|
|
// Hint line (what to do next)
|
|
showHint && config.hint ? h(Box, { marginTop: 0 },
|
|
h(Text, { color: colors.muted, dimColor: true },
|
|
`↳ ${config.hint}`
|
|
)
|
|
) : null
|
|
);
|
|
};
|
|
|
|
export default FlowRibbon;
|
|
export { FlowRibbon, PHASE_CONFIG };
|