feat(recipe): add measurement Recipe agent, streaming, and Canvas/Recipe tabs#270
feat(recipe): add measurement Recipe agent, streaming, and Canvas/Recipe tabs#270tnkshuuhei wants to merge 11 commits into
Conversation
Introduce a recipe pipeline that turns each Output/Outcome metric from the logic model into a practitioner-ready measurement plan: ordered steps, data collection method, required resources, frequency, target value, cautions and stakeholders. - types: RecipeMetricGuidanceSchema, RecipeSchema, RecipeMetricContext - recipeTool: enforces one structured guidance entry per input metric - recipeAgent: single batched call, respects user-edited measurement methods, preserves the user's working language - recipeWorkflow: single step with 2-retry policy, validates tool output, attaches generation timestamp - mastra/index: register recipeAgent and recipeWorkflow
Expose the recipe workflow over Server-Sent Events so the UI can show
incremental progress during the ~1-2 minute generation.
- POST /api/recipe/stream: validates the request, runs recipeWorkflow,
emits step-start / step-finish / step-error / recipe-complete /
recipe-error events. Mirrors the existing workflow stream route's
error categorization and 5-minute serverless timeout.
- hooks/useRecipeStream: client-side SSE consumer with abort support,
exposes { status, recipe, error, start, reset }.
- types/recipe-events: shared discriminated-union event type.
Render a Recipe into a single downloadable HTML file. Self-contained means inline CSS, inline base64 image, system fonts — no external resources, no font fetch, no CORS surface. Document title and main heading use a constant "MUSE RECIPE" so the placeholder branding can be swapped without touching templates. - Per-locale labels (en, ja) for section titles and field names - Outputs / Short-term Outcomes / Intermediate Outcomes sections - Per-metric card: steps (ordered list), data-collection method, frequency + target value (paired), required resources, stakeholders, cautions - HTML escape on every dynamic value (XSS-safe) - @media print rules: A4, 16mm margins, page-break-inside: avoid on metric cards - downloadRecipeHtml: Blob + object URL + auto-click pattern - 14 unit tests covering doctype, lang attribute, MUSE RECIPE constants, XSS escaping, image inline, section grouping, locale switching, empty-section skipping
Add the user-facing entry point that turns the canvas state into a downloadable HTML recipe. - RecipeDialog: filters canvas nodes down to Output / Outcome metrics, derives a logic-model title from the impact card (when present), drives useRecipeStream, captures a high-resolution canvas PNG via useCanvasImage, then composes the HTML via the dynamically imported generator and triggers download. Handles the no-metrics, generating, rendering, success, and error states. - CanvasToolbar: add Download Recipe (HTML) item with BookOpen icon to the dropdown menu; refuses to open when the canvas is empty. - i18n (en, ja): recipe namespace covering dialog copy and toast messages.
The RecipeDialog modal was a one-shot flow that auto-downloaded the HTML and lost the recipe on close. This switches the canvas page to a tabbed layout (Canvas / Recipe) so the recipe stays in view, separates the regenerate and download actions in the toolbar, and surfaces a stale indicator when the logic model is edited after generation. - Add RecipeProvider wrapping CanvasProvider so semantic mutations (addCard / updateCard / onConnect / edge remove / clear) can mark the recipe stale without false positives from React Flow measure/select churn - Add RecipeView (shadcn-based JSX) and RecipePanel (state machine UI for empty / waiting / running / success / stale / error) - Wire pendingRecipeAutoStartRef in CanvasContext so a later auto-start signal can fire triggerGeneration after the canvas finishes settling - Extract recipe metric helpers to lib/recipe-helpers.ts - Keep self-contained HTML generation as the download format; the in-tab JSX view and the downloadable HTML intentionally remain separate so their outputs can be compared before deciding on a single source forceMount + data-[state=inactive]:hidden keeps React Flow's viewport state intact across tab switches.
Adds an "Also generate recipe" switch to the generate dialog (default OFF). When enabled and metrics are also enabled, the recipe stream auto-starts once the canvas finishes settling — the auto-fire path in CanvasContext was already in place; this commit arms it from the form. Recipe requires metrics as input, so the switch is force-disabled with a tooltip explaining the prerequisite whenever the Metrics toggle is OFF.
- react-flow-architecture.md: provider tree, Recipe tab state machine, stale-detection carve-outs (no false positives from onNodesChange), the direct chain that fires the recipe after auto-layout when the generate dialog opted in - frontend-map.md: new RecipePanel / RecipeView / RecipeContext modules, useRecipeStream hook, lib/recipe-helpers and lib/generate-recipe-html - api-routes.md: add /api/recipe/stream row + recipe schemas - mastra-agents.md: new Recipe Workflow section, enableRecipe addition to the generate dialog Options list, recipe files in File References
…aware actions Merge the previous CanvasToolbar and TabsList rows into a single UnifiedHeader. Action buttons (Generate / Add card / Regenerate / Download HTML / Retry) are now driven by ContextActions, which switches based on activeTab and recipe.phase. The More dropdown is reorganized into labelled Canvas / Recipe / Danger sections so the same recipe operations remain reachable from both the primary header and the menu.
…ipeView RecipePanel now renders document content only — the sticky toolbar (Regenerate / Download HTML) and the duplicate stale badge/banner are removed because those actions live in UnifiedHeader. RecipeView accepts a stale prop and surfaces it as a chip in the meta row plus a single alert below the document header, replacing the previous three-way stale representation.
…tale UI Update the canvas architecture docs to reflect the unified header (tabs + context actions + sectioned More dropdown), the dual access paths for Recipe operations (header buttons + Recipe section of the dropdown), and the new stale chip + alert pattern rendered by RecipeView. Refresh component references in frontend-map.md and mastra-agents.md so the toolbar wording matches the new structure.
…processing Changed the loading message in RecipePanel to display the number of metrics being processed instead of the current step ID. Updated corresponding translations in English and Japanese for consistency.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Code Review - feat(recipe): add measurement Recipe agent, streaming, and Canvas/Recipe tabsOverviewThis is a well-structured addition of a measurement Recipe feature: a Mastra agent/workflow generates actionable step-by-step guidance for every Output/Outcome metric, streamed via SSE and rendered in a new Recipe tab alongside the existing canvas. Provider nesting is clearly documented, the stale-detection strategy is thoughtful, and the HTML export has solid XSS protection with good unit tests. A few issues worth addressing before merge. Potential Bug - Metric mutations do not trigger markStaleThe stale-detection wiring wraps card-level operations (addCard, updateCard, deleteCard, onConnect, plus inline onContentChange / onDeleteCard). But the recipe is driven by metrics, not card titles, so the more critical mutations are adding/removing/editing a metric within a card. addMetric, removeMetric, and updateMetric (canvas operations that mutate cardMetrics) are NOT wrapped with markStale. If a user generates a recipe, then adds a metric to an output card, the recipe tab shows no stale indicator even though the output will be out of sync. These should be wrapped the same way wrappedAddCard etc. are in CanvasContext.tsx. Testing policy gap - pure functions and Mastra tools lack testsPer the project policy in CLAUDE.md: "any new pure function, utility, API handler, or Mastra tool should land with Vitest coverage." Missing coverage:
generate-recipe-html.test.ts is a good example to follow - the same pattern works for helpers. Dead i18n keysThree keys added to the canvas namespace appear unused:
These should either be removed or wired up. Orphaned translation keys cause confusion and break next-intl missing-key warnings. Hardcoded sentinel string in workflowIn mastra/workflows/recipe-workflow.ts: if (m.existingMeasurementMethod && m.existingMeasurementMethod !== "To be defined") {"To be defined" is a magic string tightly coupled to whatever default the metric creation UI inserts. If that string ever changes, the recipe prompt will silently start including placeholder text in the agent context. Extract it to a named constant (e.g. METRIC_PLACEHOLDER_VALUE) and import it from shared types or a constants file. 30-metric limit not surfaced in the UIThe API and workflow both cap at 30 metrics, which is correct for safety. However, if a canvas has 31+ metrics, the request returns a 400 and the UI shows a generic error. Consider:
Minor issuescancelWaiting exported but never called - RecipeContextValue exposes cancelWaiting but nothing in the diff calls it. Remove it or add a TODO comment if intended for future use. as unknown as in workflow - the double-cast in recipe-workflow.ts when calling recipeAgent.generate(...) signals a Mastra type mismatch. Not a runtime risk, but a comment explaining why the cast is needed would help future callers. recipeTool.ts execute cast - inputData as { items: Recipe["items"] } bypasses Zod-inferred types. If Mastra's execute callback does not propagate inputSchema types automatically, a short comment would make the intent clear. What's working well
|
Summary
UnifiedHeaderに統合してコンテキスト応じたアクションを集約、RecipePanelのツールバーを撤去してstale UIをRecipeViewに集約。