Implement graceful recipe error handling with filename display (#4363)

Signed-off-by: Jon Andersen <jon.andersen.se@gmail.com>
This commit is contained in:
Jon Andersen
2025-08-27 11:02:58 -04:00
committed by GitHub
parent 99fa6289c4
commit e79c7663a7
2 changed files with 86 additions and 60 deletions
+83 -60
View File
@@ -372,67 +372,90 @@ Parameters you can use:
} }
}; };
// Render a recipe item // Render a recipe item with error handling
const RecipeItem = ({ savedRecipe }: { savedRecipe: SavedRecipe }) => ( const RecipeItem = ({ savedRecipe }: { savedRecipe: SavedRecipe }) => {
<Card className="py-2 px-4 mb-2 bg-background-default border-none hover:bg-background-muted cursor-pointer transition-all duration-150"> try {
<div className="flex justify-between items-start gap-4"> return (
<div className="min-w-0 flex-1"> <Card className="py-2 px-4 mb-2 bg-background-default border-none hover:bg-background-muted cursor-pointer transition-all duration-150">
<div className="flex items-center gap-2 mb-1"> <div className="flex justify-between items-start gap-4">
<h3 className="text-base truncate max-w-[50vw]">{savedRecipe.recipe.title}</h3> <div className="min-w-0 flex-1">
{savedRecipe.isGlobal ? ( <div className="flex items-center gap-2 mb-1">
<Globe className="w-4 h-4 text-text-muted flex-shrink-0" /> <h3 className="text-base truncate max-w-[50vw]">{savedRecipe.recipe.title}</h3>
) : ( {savedRecipe.isGlobal ? (
<Folder className="w-4 h-4 text-text-muted flex-shrink-0" /> <Globe className="w-4 h-4 text-text-muted flex-shrink-0" />
)} ) : (
</div> <Folder className="w-4 h-4 text-text-muted flex-shrink-0" />
<p className="text-text-muted text-sm mb-2 line-clamp-2"> )}
{savedRecipe.recipe.description} </div>
</p> <p className="text-text-muted text-sm mb-2 line-clamp-2">
<div className="flex items-center text-xs text-text-muted"> {savedRecipe.recipe.description}
<Calendar className="w-3 h-3 mr-1" /> </p>
{savedRecipe.lastModified.toLocaleDateString()} <div className="flex items-center text-xs text-text-muted">
</div> <Calendar className="w-3 h-3 mr-1" />
</div> {savedRecipe.lastModified.toLocaleDateString()}
</div>
</div>
<div className="flex items-center gap-2 shrink-0"> <div className="flex items-center gap-2 shrink-0">
<Button <Button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleLoadRecipe(savedRecipe); handleLoadRecipe(savedRecipe);
}} }}
size="sm" size="sm"
className="h-8" className="h-8"
> >
<Bot className="w-4 h-4 mr-1" /> <Bot className="w-4 h-4 mr-1" />
Use Use
</Button> </Button>
<Button <Button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handlePreviewRecipe(savedRecipe); handlePreviewRecipe(savedRecipe);
}} }}
variant="outline" variant="outline"
size="sm" size="sm"
className="h-8" className="h-8"
> >
<FileText className="w-4 h-4 mr-1" /> <FileText className="w-4 h-4 mr-1" />
Preview Preview
</Button> </Button>
<Button <Button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
handleDeleteRecipe(savedRecipe); handleDeleteRecipe(savedRecipe);
}} }}
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-8 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20" className="h-8 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
</Button> </Button>
</div> </div>
</div> </div>
</Card> </Card>
); );
} catch (error) {
// Error row showing failed to read file with filename and error details
return (
<Card className="py-2 px-4 mb-2 bg-red-50 border border-red-200 dark:bg-red-900/20 dark:border-red-800">
<div className="flex justify-between items-start gap-4">
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2 mb-1">
<AlertCircle className="w-4 h-4 text-red-500 flex-shrink-0" />
<h3 className="text-base text-red-700 dark:text-red-300">
Failed to read file: {savedRecipe.filename}
</h3>
</div>
<p className="text-red-600 dark:text-red-400 text-sm">
{error instanceof Error ? error.message : 'Unknown error'}
</p>
</div>
</div>
</Card>
);
}
};
// Render skeleton loader for recipe items // Render skeleton loader for recipe items
const RecipeSkeleton = () => ( const RecipeSkeleton = () => (
+3
View File
@@ -12,6 +12,7 @@ export interface SavedRecipe {
isGlobal: boolean; isGlobal: boolean;
lastModified: Date; lastModified: Date;
isArchived?: boolean; isArchived?: boolean;
filename: string; // The actual filename used
} }
/** /**
@@ -66,6 +67,7 @@ async function loadRecipeFromFile(
return { return {
...recipeData, ...recipeData,
isGlobal: isGlobal, isGlobal: isGlobal,
filename: recipeName,
}; };
} catch (error) { } catch (error) {
console.warn(`Failed to load recipe from ${filePath}:`, error); console.warn(`Failed to load recipe from ${filePath}:`, error);
@@ -112,6 +114,7 @@ export async function saveRecipe(recipe: Recipe, options: SaveRecipeOptions): Pr
// Create saved recipe object // Create saved recipe object
const savedRecipe: SavedRecipe = { const savedRecipe: SavedRecipe = {
name: sanitizedName, name: sanitizedName,
filename: sanitizedName,
recipe: recipe, recipe: recipe,
isGlobal: global, isGlobal: global,
lastModified: new Date(), lastModified: new Date(),