Release v1.01 Enhanced: Vi Control, TUI Gen5, Core Stability
This commit is contained in:
138
bin/ui/components/FlowRibbon.mjs
Normal file
138
bin/ui/components/FlowRibbon.mjs
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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 };
|
||||
Reference in New Issue
Block a user