Files
homaroma/.agents/explorer_m2_1/analysis.md
T
2026-06-28 21:34:58 -04:00

8.8 KiB

Milestone 2: Backend & DB — Menu Images Field Addition Strategy

Explorer 1 Analysis Report

This report outlines the precise implementation strategy for adding optional menu images fields (rootImage on Menu, and columnImage / hoverImage on MenuItem) across the codebase.


1. Prisma Schema Changes (apps/backend/prisma/schema.prisma)

We will add the new optional image fields to the Menu and MenuItem models.

Model Menu Updates (around Line 638)

model Menu {
  id        Int        @id @default(autoincrement())
  name      String     @unique // e.g., "header", "footer"
  payload   Json?
  rootImage String?    // <-- Added optional root image for the menu
  isActive  Boolean    @default(true)
  items     MenuItem[]
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt
}

Model MenuItem Updates (around Line 648)

model MenuItem {
  id           Int                   @id @default(autoincrement())
  menuId       Int
  menu         Menu                  @relation(fields: [menuId], references: [id], onDelete: Cascade)
  label        String
  url          String
  order        Int                   @default(0)
  columnImage  String?               // <-- Added optional column image URL
  hoverImage   String?               // <-- Added optional hover image URL
  translations MenuItemTranslation[]
  createdAt    DateTime              @default(now())
  updatedAt    DateTime              @updatedAt
}

Migration Commands Required

  1. Run the migration to generate and apply the schema changes in the database development environment:
    cd apps/backend && bunx prisma migrate dev --name add_menu_image_fields
    
    Note: Using bunx matches the workspace runner environment (as specified in package.json dev scripts).
  2. Prisma client will be automatically generated upon a successful migration. If not, generate manually using:
    cd apps/backend && bunx prisma generate
    

2. Shared Types Updates (packages/types/index.ts)

Modify the interfaces to expose the newly added fields.

Interface MenuItem (around Line 195)

export interface MenuItem {
  id: number
  label: string
  url: string
  order: number
  columnImage?: string | null   // <-- Added
  hoverImage?: string | null    // <-- Added
  translations?: any[]
}

Interface Menu (around Line 203)

export interface Menu {
  id: number
  name: string
  items: MenuItem[]
  rootImage?: string | null     // <-- Added
  payload?: any
}

3. Backend Route Updates (apps/backend/src/routes/menus.ts)

Update the Elysia endpoints to handle, validate, save, and return these new fields.

1) POST / Endpoint

  • Body Destructuring & Destructuring Cast (around Line 34):
    const { name, rootImage, items } = body as { 
      name: string; 
      rootImage?: string; 
      items?: { label: string; url: string; order: number; columnImage?: string; hoverImage?: string }[] 
    };
    
  • Database Creation Payload (around Line 35):
    const menu = await prisma.menu.create({
      data: {
        name,
        rootImage,
        items: items ? {
          create: items
        } : undefined
      },
      include: { items: true }
    });
    
  • Validation Schema (around Line 50):
    body: t.Object({
      name: t.String(),
      rootImage: t.Optional(t.Nullable(t.String())),
      items: t.Optional(t.Array(t.Object({
        label: t.String(),
        url: t.String(),
        order: t.Number(),
        columnImage: t.Optional(t.Nullable(t.String())),
        hoverImage: t.Optional(t.Nullable(t.String()))
      })))
    })
    

2) PUT /:name Endpoint

  • Body Destructuring & Type Casting (around Line 62):
    const { items, payload, isActive, rootImage } = body as { 
      payload?: any;
      isActive?: boolean;
      rootImage?: string | null;
      items: { 
        id?: number; 
        label: string; 
        url: string; 
        order: number; 
        columnImage?: string | null;
        hoverImage?: string | null;
        translations?: { langCode: string; label: string }[] 
      }[] 
    };
    
  • Map rootImage to updateData (around Line 69):
    const updateData: any = {};
    if (payload !== undefined) updateData.payload = payload;
    if (isActive !== undefined) updateData.isActive = isActive;
    if (rootImage !== undefined) updateData.rootImage = rootImage;
    
  • Transaction - Item Update (around Line 90):
    await tx.menuItem.update({
      where: { id: item.id },
      data: { 
        label: item.label, 
        url: item.url, 
        order: item.order,
        columnImage: item.columnImage !== undefined ? item.columnImage : undefined,
        hoverImage: item.hoverImage !== undefined ? item.hoverImage : undefined
      }
    });
    
  • Transaction - Item Create (around Line 105):
    await tx.menuItem.create({
      data: {
        menuId: menu!.id,
        label: item.label,
        url: item.url,
        order: item.order,
        columnImage: item.columnImage,
        hoverImage: item.hoverImage,
        translations: item.translations?.length ? {
          create: item.translations.map(t => ({
            langCode: t.langCode,
            label: t.label
          }))
        } : undefined
      }
    });
    
  • Validation Schema (around Line 133):
    body: t.Object({
      payload: t.Optional(t.Any()),
      isActive: t.Optional(t.Boolean()),
      rootImage: t.Optional(t.Nullable(t.String())),
      items: t.Array(t.Object({
        id: t.Optional(t.Number()),
        label: t.String(),
        url: t.String(),
        order: t.Number(),
        columnImage: t.Optional(t.Nullable(t.String())),
        hoverImage: t.Optional(t.Nullable(t.String())),
        translations: t.Optional(t.Array(t.Object({
          langCode: t.String(),
          label: t.String()
        })))
      }))
    })
    

4. MCP Server Tools Updates

1) Tool Input Validation (apps/mcp-server/menu-tools.ts around Line 181)

Add input check in validateMenuItems:

if (item.columnImage !== undefined && item.columnImage !== null && typeof item.columnImage !== "string") {
  issues.push(`items[${index}].columnImage must be a string when provided`);
}
if (item.hoverImage !== undefined && item.hoverImage !== null && typeof item.hoverImage !== "string") {
  issues.push(`items[${index}].hoverImage must be a string when provided`);
}

2) Tool Schema and Handler (apps/mcp-server/menu-toolkit.ts)

  • upsert_menu Input Schema: Add rootImage property to top-level properties:
    "rootImage": {
      "type": "string",
      "description": "Optional root image URL for the menu"
    }
    
    Add columnImage and hoverImage to item properties:
    "columnImage": {
      "type": "string",
      "description": "Optional column image URL for the menu item"
    },
    "hoverImage": {
      "type": "string",
      "description": "Optional hover image URL for the menu item"
    }
    
  • handleMenuTool logic for upsert_menu: Check and pass rootImage in the body payload (around Line 227):
    if (params.rootImage !== undefined) {
      body.rootImage = params.rootImage;
    } else if (existingMenu?.rootImage !== undefined) {
      body.rootImage = existingMenu.rootImage;
    }
    
    Update dryRun output preview (around Line 253):
    preview: {
      name: params.name,
      itemCount: items.length,
      hasPayload: !!body.payload,
      isActive: body.isActive,
      rootImage: body.rootImage
    }
    
    Update actual successful response payload to expose rootImage (around Line 273):
    text: JSON.stringify(
      {
        success: true,
        operation: "upsert_menu",
        name: res.data.name,
        isActive: res.data.isActive,
        rootImage: res.data.rootImage,
        itemCount: res.data.items?.length || 0,
        hasPayload: !!res.data.payload
      },
      null,
      2
    )
    

5. Potential Risks & Mitigation

  1. Migration Conflicts:
    • Risk: Running migrations in environments where database instances are out of sync can lead to conflicts.
    • Mitigation: Ensure other developers pull changes and run bunx prisma migrate dev before starting work.
  2. Backward Compatibility:
    • Risk: Existing menus or components that do not expect nullable/optional fields might break if type checks are strictly checking properties.
    • Mitigation: Use String? / optional properties which evaluate to null or undefined. Ensure frontend consumption handles null values gracefully (e.g. using optional chaining menu.rootImage and fallback components).
  3. Monorepo Dependency Syncing:
    • Risk: If @homaroma/types is updated but workspace dependencies aren't built or re-linked, typescript compilation might fail on clients.
    • Mitigation: Run workspace-wide build or type checks (npm run build or bunx tsc) after updating the package.