jamiepine/voicebox · warning · Error
No profile selected
Error message
No profile selected
What it means
Thrown by the compose mutation in FloatingGenerateBox if selectedProfileId is falsy when composeWithPersonality is called. It is a precondition guard: the API call requires a profile id, so composing without one is a programming/state error. The onError handler shows a destructive toast with the message (or a localized fallback).
Source
Thrown at app/src/components/Generation/FloatingGenerateBox.tsx:60
const setSelectedEngine = useUIStore((state) => state.setSelectedEngine);
const { data: selectedProfile } = useProfile(selectedProfileId || '');
const { data: profiles } = useProfiles();
const [isExpanded, setIsExpanded] = useState(false);
const [isInstructExpanded, setIsInstructExpanded] = useState(false);
const [selectedPresetId, setSelectedPresetId] = useState<string | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
const matchRoute = useMatchRoute();
const isStoriesRoute = matchRoute({ to: '/stories' });
const selectedStoryId = useStoryStore((state) => state.selectedStoryId);
const trackEditorHeight = useStoryStore((state) => state.trackEditorHeight);
const { data: currentStory } = useStory(selectedStoryId);
const addPendingStoryAdd = useGenerationStore((s) => s.addPendingStoryAdd);
const { toast } = useToast();
const composeMutation = useMutation({
mutationFn: async () => {
if (!selectedProfileId) throw new Error('No profile selected');
return apiClient.composeWithPersonality(selectedProfileId);
},
onError: (err: Error) => {
toast({
title: t('generation.compose.failedTitle'),
description: err.message || t('generation.compose.failedDescription'),
variant: 'destructive',
});
},
});
// Fetch effect presets for the dropdown
const { data: effectPresets } = useQuery({
queryKey: ['effectPresets'],
queryFn: () => apiClient.listEffectPresets(),
});
// Calculate if track editor is visible (on stories route with items)View on GitHub (pinned to 51f49dea19)
Solutions
- Disable the Compose button until selectedProfileId is truthy.
- Auto-select a sensible default profile once the profile list resolves.
- If triggered by a shortcut/hotkey, no-op when no profile is selected instead of calling the mutation.
Example fix
// before
<button onClick={() => composeMutation.mutate()}>Compose</button>
// after
<button disabled={!selectedProfileId} onClick={() => composeMutation.mutate()}>Compose</button>; Defensive patterns
Strategy: validation
Validate before calling
const canCompose = Boolean(selectedProfileId);
// Disable Compose until a profile is chosen:
<button disabled={!canCompose} onClick={() => composeMutation.mutate()} /> Try / catch
if (!selectedProfileId) {
toast({ title: t('generation.compose.selectProfileFirst'), variant: 'destructive' });
return;
}
try {
await apiClient.composeWithPersonality(selectedProfileId);
} catch (e) {
toast({ title: t('generation.compose.failedTitle'), description: (e as Error).message, variant: 'destructive' });
} Prevention
- Disable Compose unless selectedProfileId is truthy.
- Auto-select a default profile once the list resolves.
- No-op on hotkey-triggered compose when no profile is selected.
When it happens
Trigger: The Compose button is clicked before any voice profile is selected — e.g. profile list still loading, profile was deselected/deleted, or selectedProfileId state was reset. Can also fire from a keyboard shortcut that triggers compose regardless of selection.
Common situations: Race between profile fetch and user action on first paint. Profile deleted elsewhere while the generate box stayed mounted. Story route auto-compose triggered without a default profile.
Related errors
- captures.noTranscriptError
- HTTP ${res.status}
- Failed to fetch model info: ${response.status}
- e instanceof Error ? e.message : errorMessage
- Generation is not completed
AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12).
Data as JSON: /api/errors/45a675b1b240d410.
Report an issue: GitHub.