feat(cli): unify loading phrase settings into single layout control

- Replace ui.loadingPhrases and ui.wittyPhrasePosition with ui.loadingPhraseLayout
- Supported layouts: none, tips, wit_status, wit_inline, wit_ambient, all_inline, all_ambient
- Set 'all_inline' as the new default (tips on right, wit inline)
- Update migration logic to map deprecated enableLoadingPhrases to 'none'
- Synchronize all unit tests and documentation with the new configuration model
This commit is contained in:
Keith Guerin
2026-02-28 23:37:40 -08:00
parent 3bd36ce4f0
commit e1e863dba2
12 changed files with 126 additions and 98 deletions
@@ -34,7 +34,7 @@ describe('useLoadingIndicator', () => {
initialStreamingState: StreamingState,
initialShouldShowFocusHint: boolean = false,
initialRetryStatus: RetryAttemptPayload | null = null,
loadingPhrasesMode: LoadingPhrasesMode = 'all',
loadingPhraseLayout: LoadingPhrasesMode = 'all_inline',
initialErrorVerbosity: 'low' | 'full' = 'full',
) => {
let hookResult: ReturnType<typeof useLoadingIndicator>;
@@ -55,7 +55,7 @@ describe('useLoadingIndicator', () => {
streamingState,
shouldShowFocusHint: !!shouldShowFocusHint,
retryStatus: retryStatus || null,
loadingPhrasesMode: mode,
loadingPhraseLayout: mode,
errorVerbosity,
});
return null;
@@ -65,7 +65,7 @@ describe('useLoadingIndicator', () => {
streamingState={initialStreamingState}
shouldShowFocusHint={initialShouldShowFocusHint}
retryStatus={initialRetryStatus}
mode={loadingPhrasesMode}
mode={loadingPhraseLayout}
errorVerbosity={initialErrorVerbosity}
/>,
);
@@ -84,7 +84,7 @@ describe('useLoadingIndicator', () => {
}) =>
rerender(
<TestComponent
mode={loadingPhrasesMode}
mode={loadingPhraseLayout}
errorVerbosity={initialErrorVerbosity}
{...newProps}
/>,
@@ -253,7 +253,7 @@ describe('useLoadingIndicator', () => {
StreamingState.Responding,
false,
retryStatus,
'all',
'all_inline',
'low',
);
@@ -273,7 +273,7 @@ describe('useLoadingIndicator', () => {
StreamingState.Responding,
false,
retryStatus,
'all',
'all_inline',
'low',
);
@@ -282,12 +282,12 @@ describe('useLoadingIndicator', () => {
);
});
it('should show no phrases when loadingPhrasesMode is "off"', () => {
it('should show no phrases when loadingPhraseLayout is "none"', () => {
const { result } = renderLoadingIndicatorHook(
StreamingState.Responding,
false,
null,
'off',
'none',
);
expect(result.current.currentLoadingPhrase).toBeUndefined();
@@ -20,7 +20,7 @@ export interface UseLoadingIndicatorProps {
streamingState: StreamingState;
shouldShowFocusHint: boolean;
retryStatus: RetryAttemptPayload | null;
loadingPhrasesMode?: LoadingPhrasesMode;
loadingPhraseLayout?: LoadingPhrasesMode;
customWittyPhrases?: string[];
errorVerbosity?: 'low' | 'full';
maxLength?: number;
@@ -30,7 +30,7 @@ export const useLoadingIndicator = ({
streamingState,
shouldShowFocusHint,
retryStatus,
loadingPhrasesMode,
loadingPhraseLayout,
customWittyPhrases,
errorVerbosity = 'full',
maxLength,
@@ -46,7 +46,7 @@ export const useLoadingIndicator = ({
isPhraseCyclingActive,
isWaiting,
shouldShowFocusHint,
loadingPhrasesMode,
loadingPhraseLayout,
customWittyPhrases,
maxLength,
);
@@ -21,20 +21,20 @@ const TestComponent = ({
isActive,
isWaiting,
isInteractiveShellWaiting = false,
loadingPhrasesMode = 'all',
loadingPhraseLayout = 'all_inline',
customPhrases,
}: {
isActive: boolean;
isWaiting: boolean;
isInteractiveShellWaiting?: boolean;
loadingPhrasesMode?: LoadingPhrasesMode;
loadingPhraseLayout?: LoadingPhrasesMode;
customPhrases?: string[];
}) => {
const { currentTip, currentWittyPhrase } = usePhraseCycler(
isActive,
isWaiting,
isInteractiveShellWaiting,
loadingPhrasesMode,
loadingPhraseLayout,
customPhrases,
);
return <Text>{currentTip || currentWittyPhrase}</Text>;
@@ -293,7 +293,7 @@ describe('usePhraseCycler', () => {
<TestComponent
isActive={config.isActive}
isWaiting={false}
loadingPhrasesMode="witty"
loadingPhraseLayout="wit_inline"
customPhrases={config.customPhrases}
/>
);
+24 -15
View File
@@ -18,16 +18,16 @@ export const INTERACTIVE_SHELL_WAITING_PHRASE =
* @param isActive Whether the phrase cycling should be active.
* @param isWaiting Whether to show a specific waiting phrase.
* @param shouldShowFocusHint Whether to show the shell focus hint.
* @param loadingPhrasesMode Which phrases to show: tips, witty, all, or off.
* @param loadingPhraseLayout Which phrases to show and where.
* @param customPhrases Optional list of custom phrases to use instead of built-in witty phrases.
* @param maxLength Optional maximum length for the selected phrase.
* @returns The current loading phrase.
* @returns The current tip and witty phrase.
*/
export const usePhraseCycler = (
isActive: boolean,
isWaiting: boolean,
shouldShowFocusHint: boolean,
loadingPhrasesMode: LoadingPhrasesMode = 'tips',
loadingPhraseLayout: LoadingPhrasesMode = 'all_inline',
customPhrases?: string[],
maxLength?: number,
) => {
@@ -58,7 +58,7 @@ export const usePhraseCycler = (
return;
}
if (!isActive || loadingPhrasesMode === 'off') {
if (!isActive || loadingPhraseLayout === 'none') {
setCurrentTip(undefined);
setCurrentWittyPhrase(undefined);
return;
@@ -70,10 +70,23 @@ export const usePhraseCycler = (
: WITTY_LOADING_PHRASES;
const setRandomPhrase = () => {
let currentMode = loadingPhrasesMode;
let currentMode: 'tips' | 'witty' | 'all' = 'all';
// In 'all' mode, we decide once per phrase cycle what to show
if (loadingPhrasesMode === 'all') {
if (loadingPhraseLayout === 'tips') {
currentMode = 'tips';
} else if (
loadingPhraseLayout === 'wit_status' ||
loadingPhraseLayout === 'wit_inline' ||
loadingPhraseLayout === 'wit_ambient'
) {
currentMode = 'witty';
}
// In 'all' modes, we decide once per phrase cycle what to show
if (
loadingPhraseLayout === 'all_inline' ||
loadingPhraseLayout === 'all_ambient'
) {
if (!hasShownFirstRequestTipRef.current) {
currentMode = 'tips';
hasShownFirstRequestTipRef.current = true;
@@ -85,13 +98,9 @@ export const usePhraseCycler = (
const phraseList =
currentMode === 'witty' ? wittyPhrases : INFORMATIVE_TIPS;
// If we have a maxLength, we need to account for potential prefixes.
// Tips are prefixed with "Tip: " in the Composer UI.
const adjustedMaxLength = maxLength;
const filteredList =
adjustedMaxLength !== undefined
? phraseList.filter((p) => p.length <= adjustedMaxLength)
maxLength !== undefined
? phraseList.filter((p) => p.length <= maxLength)
: phraseList;
if (filteredList.length > 0) {
@@ -105,7 +114,7 @@ export const usePhraseCycler = (
setCurrentWittyPhrase(undefined);
}
} else {
// If no phrases fit, try to fallback to a very short list or nothing
// If no phrases fit, try to fallback
setCurrentTip(undefined);
setCurrentWittyPhrase(undefined);
}
@@ -129,7 +138,7 @@ export const usePhraseCycler = (
isActive,
isWaiting,
shouldShowFocusHint,
loadingPhrasesMode,
loadingPhraseLayout,
customPhrases,
maxLength,
]);