Context System

Pass custom data to components for dynamic template rendering.

Overview

Melony's context system allows you to pass custom data to MelonyMarkdown orMelonyWidget components. This data becomes available as variables in component templates, enabling dynamic content rendering based on your application state.

Basic Usage

Pass a context object to MelonyMarkdown:

import { MelonyMarkdown } from "melony";

function Chat() {
  const context = {
    user: {
      name: "John Doe",
      email: "john@example.com",
    },
    isAuthenticated: true,
  };

  const aiResponse = `
    Welcome, {{user.name}}!
    
    <card title="Profile">
      <text value="Name: {{user.name}}" />
      <text value="Email: {{user.email}}" />
    </card>
  `;

  return <MelonyMarkdown context={context}>{aiResponse}</MelonyMarkdown>;
}

Using Context Variables

Context variables can be used anywhere in component templates:

Hello {{user.name}}!

<card title="Welcome">
  <text value="Email: {{user.email}}" />
  <text value="Status: {{isAuthenticated ? 'Logged In' : 'Guest'}}" />
</card>

Dynamic Data

Update context dynamically based on your application state:

import { useState } from "react";
import { MelonyMarkdown } from "melony";

function Dashboard() {
  const [weather, setWeather] = useState({
    temp: 72,
    condition: "Sunny",
    location: "San Francisco",
  });

  const context = {
    weather,
    currentTime: new Date().toLocaleTimeString(),
  };

  const content = `
    <card title="Dashboard">
      <text value="Time: {{currentTime}}" />
      <text value="{{weather.location}} Weather" size="lg" weight="bold" />
      <text value="{{weather.temp}}°F - {{weather.condition}}" />
    </card>
  `;

  return (
    <div>
      <MelonyMarkdown context={context}>{content}</MelonyMarkdown>
      <button onClick={() => setWeather({ ...weather, temp: weather.temp + 1 })}>
        Increase Temp
      </button>
    </div>
  );
}

Nested Objects

Context supports deeply nested objects:

const context = {
  app: {
    name: "MyApp",
    version: "1.0.0",
    settings: {
      theme: "dark",
      language: "en",
    },
  },
  user: {
    profile: {
      name: "Alice",
      preferences: {
        notifications: true,
        newsletter: false,
      },
    },
  },
};

Access nested values with dot notation:

<card title="{{app.name}} v{{app.version}}">
  <text value="Theme: {{app.settings.theme}}" />
  <text value="User: {{user.profile.name}}" />
  <text value="Notifications: {{user.profile.preferences.notifications ? 'On' : 'Off'}}" />
</card>

Arrays in Context

Pass arrays and use them with the <for> component:

const context = {
  tasks: [
    { id: 1, title: "Buy groceries", completed: false },
    { id: 2, title: "Walk dog", completed: true },
    { id: 3, title: "Code review", completed: false },
  ],
};

const content = `
  <card title="My Tasks">
    <for items="{{tasks}}">
      <row gap="md">
        <text value="{{item.title}}" flex="1" />
        <badge 
          label="{{item.completed ? 'Done' : 'Pending'}}" 
          variant="{{item.completed ? 'success' : 'warning'}}" 
        />
      </row>
    </for>
  </card>
`;

<MelonyMarkdown context={context}>{content}</MelonyMarkdown>

Functions in Context

You can pass functions to perform calculations or formatting:

const context = {
  price: 99.99,
  quantity: 3,
  formatPrice: (num: number) => `$${num.toFixed(2)}`,
  calculateTotal: (price: number, qty: number) => price * qty,
};

const content = `
  <card title="Order Summary">
    <text value="Price: {{formatPrice(price)}}" />
    <text value="Quantity: {{quantity}}" />
    <text value="Total: {{formatPrice(calculateTotal(price, quantity))}}" />
  </card>
`;

Real-World Example

Here's a complete example with authentication state:

import { MelonyProvider, MelonyMarkdown } from "melony";
import { useChat } from "ai/react";
import { useAuth } from "@/hooks/useAuth";

export default function Chat() {
  const { messages } = useChat({ api: "/api/chat" });
  const { user, isAuthenticated, logout } = useAuth();

  const context = {
    user: {
      name: user?.name || "Guest",
      email: user?.email || "",
      avatar: user?.avatar || "",
    },
    isAuthenticated,
    permissions: user?.permissions || [],
    canEdit: user?.permissions?.includes("edit") || false,
    canDelete: user?.permissions?.includes("delete") || false,
  };

  const handleAction = (action) => {
    if (action.type === "logout") {
      logout();
    }
  };

  return (
    <MelonyProvider onAction={handleAction}>
      {messages.map((m) => (
        <MelonyMarkdown key={m.id} context={context}>
          {m.content}
        </MelonyMarkdown>
      ))}
    </MelonyProvider>
  );
}

The AI can now use context variables in its responses:

Welcome back, {{user.name}}!

<card title="Your Profile">
  <text value="Email: {{user.email}}" />
  <text value="Status: {{isAuthenticated ? 'Active' : 'Guest'}}" />
  
  {{#if canEdit}}
  <button 
    label="Edit Profile" 
    action='{"type":"edit-profile"}' 
  />
  {{/if}}
  
  <button 
    label="Logout" 
    variant="outline"
    action='{"type":"logout"}' 
  />
</card>

Context with Widgets

Context also works with MelonyWidget:

const widgetTemplate = `
  <card title="{{user.name}}'s Dashboard">
    <text value="Welcome back!" />
    <text value="Last login: {{lastLogin}}" size="sm" color="muted" />
  </card>
`;

const context = {
  user: { name: "Alice" },
  lastLogin: "2024-01-15 10:30 AM",
};

<MelonyWidget context={context}>{widgetTemplate}</MelonyWidget>

Best Practices

  • Keep it Flat: Avoid deeply nested structures when possible
  • Type Safety: Define TypeScript interfaces for your context objects
  • Minimal Data: Only pass data that templates actually need
  • Immutability: Don't mutate context objects directly
  • Memoization: Use useMemo for computed context values
  • Security: Never pass sensitive data that shouldn't be exposed

TypeScript Support

Define types for your context objects:

interface UserContext {
  user: {
    name: string;
    email: string;
    avatar?: string;
  };
  isAuthenticated: boolean;
  permissions: string[];
}

const context: UserContext = {
  user: {
    name: "Alice",
    email: "alice@example.com",
  },
  isAuthenticated: true,
  permissions: ["read", "write"],
};

<MelonyMarkdown context={context}>{content}</MelonyMarkdown>