{"record":{"id":"88773c6ddb75109c","repo":"mastra-ai/mastra","slug":"plan-compound-components-must-be-rendered-inside","errorCode":null,"errorMessage":"Plan compound components must be rendered inside <Plan>.","messagePattern":"Plan compound components must be rendered inside <Plan>\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/playground-ui/src/ds/components/ai/plan/plan.tsx","lineNumber":30,"sourceCode":"\nconst DEFAULT_COLLAPSED_HEIGHT = 220;\n\ninterface PlanContextValue {\n  collapsedHeight: number;\n  isExpanded: boolean;\n  /** Whether the rendered content overflows the collapsed height (measured, not estimated). */\n  isClipped: boolean;\n  setClipped: (clipped: boolean) => void;\n  toggleExpanded: () => void;\n}\n\nconst PlanContext = createContext<PlanContextValue | null>(null);\n\nconst usePlanContext = () => {\n  const context = useContext(PlanContext);\n\n  if (!context) {\n    throw new Error('Plan compound components must be rendered inside <Plan>.');\n  }\n\n  return context;\n};\n\nexport interface PlanProps extends ComponentProps<'div'> {\n  collapsedHeight?: number;\n}\n\nexport function Plan({ children, collapsedHeight = DEFAULT_COLLAPSED_HEIGHT, className, ...props }: PlanProps) {\n  const [isExpanded, setIsExpanded] = useState(false);\n  const [isClipped, setClipped] = useState(false);\n\n  const toggleExpanded = () => {\n    setIsExpanded(current => !current);\n  };\n\n  const contextValue = {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/playground-ui/src/ds/components/ai/plan/plan.tsx#L12-L48","documentation":"The Plan component is a compound component: <Plan> provides PlanContext (collapsedHeight, isExpanded, isClipped, setClipped, toggleExpanded) and its subcomponents (e.g., Plan.Body/Content) consume it via usePlanContext. If a compound child renders without an ancestor <Plan>, the context is null and this error is thrown, since expansion/clipping state cannot be resolved.","triggerScenarios":"Rendering Plan subcomponents (which call usePlanContext) outside a <Plan> parent; composing Plan parts into a custom layout where the <Plan> wrapper was removed; conditionally rendering <Plan> but always rendering its children.","commonSituations":"Refactoring an AI plan card into a custom grid and dropping the wrapper; reusing a Plan.ClampedContent section in another view; storybook stories rendering subcomponents alone.","solutions":["Wrap all Plan compound children inside <Plan>.","Restore the <Plan> wrapper if a refactor removed it while keeping the child components.","Pass expansion state via props instead of using Plan compounds if you need them in a different tree.","Render children through Plan's render/children API so the context is guaranteed present."],"exampleFix":"// before\n<Plan.Content isExpanded={isExpanded} /> {/* throws */}\n\n// after\n<Plan title=\"Agent plan\">\n  <Plan.Content />\n</Plan>","handlingStrategy":"validation","validationCode":"const plan = useContext(PlanContext);\nif (!plan) {\n  // render a standalone non-compound view instead of Plan subcomponents\n}","typeGuard":"const hasPlanContext = (v: PlanContextValue | null): v is PlanContextValue => v != null;","tryCatchPattern":"try {\n  const plan = usePlanContext();\n  plan.toggleExpanded();\n} catch {\n  // outside <Plan>: use local expansion state\n}","preventionTips":["Treat Plan subcomponents as private to the <Plan> wrapper; never import them standalone.","When refactoring plan layouts, keep the <Plan> root as the outermost element.","Provide expansion state via props for usage outside the compound.","Verify stories render the full <Plan> composition."],"tags":["react","context","compound-components","provider-missing"],"backgroundTag":"missing-react-context-provider","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}