Big changes and fixes. Uses action history. Improve role selection. Remove unused logs

This commit is contained in:
2026-02-08 15:41:14 +00:00
parent da096af0fa
commit df57f1b9ad
16 changed files with 1250 additions and 1181 deletions

View File

@@ -14,7 +14,20 @@ import { colors, logoSmall } from '../theme.js';
// XO Imports
import { generateTemplateIdentifier } from '@xo-cash/engine';
import type { XOTemplate, XOTemplateActionRoleRequirement, XOTemplateStartingActions } from '@xo-cash/types';
import type { XOTemplate } from '@xo-cash/types';
/**
* A unique starting action (deduplicated by action identifier).
* Multiple roles that can start the same action are counted
* but not shown as separate entries — role selection happens
* inside the Action Wizard.
*/
interface UniqueStartingAction {
actionIdentifier: string;
name: string;
description?: string;
roleCount: number;
}
/**
* Template item with metadata.
@@ -22,7 +35,7 @@ import type { XOTemplate, XOTemplateActionRoleRequirement, XOTemplateStartingAct
interface TemplateItem {
template: XOTemplate;
templateIdentifier: string;
startingActions: XOTemplateStartingActions;
startingActions: UniqueStartingAction[];
}
/**
@@ -59,8 +72,30 @@ export function TemplateListScreen(): React.ReactElement {
const loadedTemplates = await Promise.all(
templateList.map(async (template) => {
const templateIdentifier = generateTemplateIdentifier(template);
const startingActions = await appService.engine.listStartingActions(templateIdentifier);
return { template, templateIdentifier, startingActions };
const rawStartingActions = await appService.engine.listStartingActions(templateIdentifier);
// Deduplicate by action identifier — role selection
// is handled inside the Action Wizard, not here.
const actionMap = new Map<string, UniqueStartingAction>();
for (const sa of rawStartingActions) {
if (actionMap.has(sa.action)) {
actionMap.get(sa.action)!.roleCount++;
} else {
const actionDef = template.actions?.[sa.action];
actionMap.set(sa.action, {
actionIdentifier: sa.action,
name: actionDef?.name || sa.action,
description: actionDef?.description,
roleCount: 1,
});
}
}
return {
template,
templateIdentifier,
startingActions: Array.from(actionMap.values()),
};
})
);
@@ -86,6 +121,7 @@ export function TemplateListScreen(): React.ReactElement {
/**
* Handles action selection.
* Navigates to the Action Wizard where the user will choose their role.
*/
const handleActionSelect = useCallback(() => {
if (!currentTemplate || currentActions.length === 0) return;
@@ -93,11 +129,10 @@ export function TemplateListScreen(): React.ReactElement {
const action = currentActions[selectedActionIndex];
if (!action) return;
// Navigate to action wizard with selected template and action
// Navigate to the Action Wizard — role selection happens there
navigate('wizard', {
templateIdentifier: currentTemplate.templateIdentifier,
actionIdentifier: action.action,
roleIdentifier: action.role,
actionIdentifier: action.actionIdentifier,
template: currentTemplate.template,
});
}, [currentTemplate, currentActions, selectedActionIndex, navigate]);
@@ -211,20 +246,19 @@ export function TemplateListScreen(): React.ReactElement {
}
// Starting actions state
return currentActions.map((action, index) => {
const actionDef = currentTemplate.template.actions?.[action.action];
const name = actionDef?.name || action.action;
return (
<Text
key={`${action.action}-${action.role}`}
color={index === selectedActionIndex ? colors.focus : colors.text}
bold={index === selectedActionIndex}
>
{index === selectedActionIndex && focusedPanel === 'actions' ? '▸ ' : ' '}
{index + 1}. {name} (as {action.role})
</Text>
);
});
return currentActions.map((action, index) => (
<Text
key={action.actionIdentifier}
color={index === selectedActionIndex ? colors.focus : colors.text}
bold={index === selectedActionIndex}
>
{index === selectedActionIndex && focusedPanel === 'actions' ? '▸ ' : ' '}
{index + 1}. {action.name}
{action.roleCount > 1
? ` (${action.roleCount} roles)`
: ''}
</Text>
));
})()}
</Box>
@@ -280,50 +314,34 @@ export function TemplateListScreen(): React.ReactElement {
const action = currentActions[selectedActionIndex];
if (!action) return null;
const actionDef = currentTemplate.template.actions?.[action.action];
const roleDef = currentTemplate.template.roles?.[action.role];
// Collect all roles that can start this action
const startEntries = (currentTemplate.template.start ?? [])
.filter((s) => s.action === action.actionIdentifier);
// if (!actionDef || !roleDef) return null;
const [_roleName, role] = Object.entries(actionDef?.roles ?? {}).find(([roleId, role]) => roleId === action.role) || [];
console.log(JSON.stringify(role, null, 2));
const variableKeys = role?.requirements?.variables || []
console.log('variables', variableKeys);
return (
<>
<Text color={colors.text} bold>
{actionDef?.name || action.action}
{action.name}
</Text>
<Text color={colors.textMuted}>
{actionDef?.description || 'No description available'}
{action.description || 'No description available'}
</Text>
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>
Role: {roleDef?.name || action.role}
</Text>
{roleDef?.description && (
<Text color={colors.textMuted}>
{' '}{roleDef.description}
</Text>
)}
</Box>
{/* Display variables if available */}
{
variableKeys.length > 0 && (
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>Variables:</Text>
{variableKeys.map((variableKey) => (
<Text key={variableKey} color={colors.text}>
- {currentTemplate.template.variables?.[variableKey]?.name || variableKey}: {currentTemplate.template.variables?.[variableKey]?.description || 'No description'}
{/* List available roles for this action */}
{startEntries.length > 0 && (
<Box marginTop={1} flexDirection='column'>
<Text color={colors.text}>Available Roles:</Text>
{startEntries.map((entry) => {
const roleDef = currentTemplate.template.roles?.[entry.role];
return (
<Text key={entry.role} color={colors.textMuted}>
{' '}- {roleDef?.name || entry.role}
{roleDef?.description ? `: ${roleDef.description}` : ''}
</Text>
))}
</Box>
)
}
);
})}
</Box>
)}
</>
);
})()}