mastra-ai/mastra · error · Error

Plan compound components must be rendered inside <Plan>.

Error message

Plan compound components must be rendered inside <Plan>.

What it means

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.

Source

Thrown at packages/playground-ui/src/ds/components/ai/plan/plan.tsx:30

const DEFAULT_COLLAPSED_HEIGHT = 220;

interface PlanContextValue {
  collapsedHeight: number;
  isExpanded: boolean;
  /** Whether the rendered content overflows the collapsed height (measured, not estimated). */
  isClipped: boolean;
  setClipped: (clipped: boolean) => void;
  toggleExpanded: () => void;
}

const PlanContext = createContext<PlanContextValue | null>(null);

const usePlanContext = () => {
  const context = useContext(PlanContext);

  if (!context) {
    throw new Error('Plan compound components must be rendered inside <Plan>.');
  }

  return context;
};

export interface PlanProps extends ComponentProps<'div'> {
  collapsedHeight?: number;
}

export function Plan({ children, collapsedHeight = DEFAULT_COLLAPSED_HEIGHT, className, ...props }: PlanProps) {
  const [isExpanded, setIsExpanded] = useState(false);
  const [isClipped, setClipped] = useState(false);

  const toggleExpanded = () => {
    setIsExpanded(current => !current);
  };

  const contextValue = {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Wrap all Plan compound children inside <Plan>.
  2. Restore the <Plan> wrapper if a refactor removed it while keeping the child components.
  3. Pass expansion state via props instead of using Plan compounds if you need them in a different tree.
  4. Render children through Plan's render/children API so the context is guaranteed present.

Example fix

// before
<Plan.Content isExpanded={isExpanded} /> {/* throws */}

// after
<Plan title="Agent plan">
  <Plan.Content />
</Plan>
Defensive patterns

Strategy: validation

Validate before calling

const plan = useContext(PlanContext);
if (!plan) {
  // render a standalone non-compound view instead of Plan subcomponents
}

Type guard

const hasPlanContext = (v: PlanContextValue | null): v is PlanContextValue => v != null;

Try / catch

try {
  const plan = usePlanContext();
  plan.toggleExpanded();
} catch {
  // outside <Plan>: use local expansion state
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/88773c6ddb75109c. Report an issue: GitHub.