This commit is contained in:
Your Name committed 2026-06-28 21:34:58 -04:00
1 parent 3965f4f4da
commit 70877882b0
82 files changed
+4353 -186

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
# BRIEFING — 2026-06-28T21:20:34-04:00
## Mission
Analyze the codebase and formulate a precise implementation strategy for adding image fields to Menu and MenuItem Prisma schema, shared types, backend routes, and MCP server tools.
## 🔒 My Identity
- Archetype: Explorer 2 (Backend & DB Specialist)
- Roles: Read-only investigator
- Working directory: /home/ti/git/homaroma/.agents/explorer_m2_2
- Original parent: 8e727cf6-d863-41ec-9bd2-f8dead86db2a
- Milestone: Milestone 2: Backend & DB
## 🔒 Key Constraints
- Read-only investigation — do NOT implement
- Follow AGENTS.md guidelines (do not guess field names, API contracts, routes, schema, etc.)
## Current Parent
- Conversation ID: 8e727cf6-d863-41ec-9bd2-f8dead86db2a
- Updated: 2026-06-28T21:25:00-04:00
## Investigation State
- **Explored paths**:
- `apps/backend/prisma/schema.prisma`
- `packages/types/index.ts`
- `apps/backend/src/routes/menus.ts`
- `apps/mcp-server/menu-tools.ts`
- `apps/mcp-server/menu-toolkit.ts`
- `apps/spa/src/store/menuStore.ts`
- `apps/website/src/components/navigation/menuTypes.ts`
- **Key findings**:
- Found precise code blocks and line numbers for models/schemas.
- Formulated full diff and migration scripts.
- Checked integration of new properties in the MCP Server tools.
- **Unexplored areas**: None.
## Key Decisions Made
- Focused on minimal, backwards-compatible schema modifications using nullable (`String?` / optional) fields to ensure no disruption to existing data.
## Artifact Index
- /home/ti/git/homaroma/.agents/explorer_m2_2/analysis.md — Main analysis and implementation strategy report
+11
View File
@@ -0,0 +1,11 @@
## 2026-06-28T21:20:34Z
You are Explorer 2 for Milestone 2: Backend & DB.
Your working directory is /home/ti/git/homaroma/.agents/explorer_m2_2.
Please analyze the codebase and formulate a precise implementation strategy for updating:
1. Prisma schema (`apps/backend/prisma/schema.prisma`): Add rootImage (String?, optional) to Menu, and columnImage (String?, optional) and hoverImage (String?, optional) to MenuItem. Explain what migration commands are needed.
2. Shared types (`packages/types/index.ts`): Expose the new fields.
3. Backend route (`apps/backend/src/routes/menus.ts`): Modify the endpoints (GET, POST, etc.) to handle, validate, save, and return these fields.
4. MCP Server tools (`apps/mcp-server/menu-tools.ts` and `apps/mcp-server/menu-toolkit.ts`): Check if any MCP tools need updating to support these new fields (both inputs and return values).
Read these files, verify their structure, check zod validators or Prisma types, and write your report to /home/ti/git/homaroma/.agents/explorer_m2_2/analysis.md. Summarize your findings, the exact file changes needed (with lines if possible), and any potential risks. Do not implement.
Once done, send a message back with your findings and the path to your analysis.md.
+285
View File
@@ -0,0 +1,285 @@
# Implementation Strategy Report — Menu & MenuItem Image Fields
This report outlines the precise implementation strategy for adding three new optional image fields to the menu system: `rootImage` for `Menu`, and `columnImage` & `hoverImage` for `MenuItem`.
---
## 1. Prisma Schema Changes (`apps/backend/prisma/schema.prisma`)
We will add the optional/nullable image fields directly to the `Menu` and `MenuItem` database models.
### Proposed Diff
```diff
diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma
--- a/apps/backend/prisma/schema.prisma
+++ b/apps/backend/prisma/schema.prisma
@@ -638,6 +638,7 @@ model Menu {
id Int @id @default(autoincrement())
name String @unique // e.g., "header", "footer"
+ rootImage String?
payload Json?
isActive Boolean @default(true)
items MenuItem[]
@@ -648,6 +649,8 @@ 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?
+ hoverImage String?
translations MenuItemTranslation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
```
### Required Migration Commands
1. **Development Environment:**
Run the following command inside `apps/backend/` to generate a database migration and update the Prisma client:
```bash
bun x prisma migrate dev --name add_menu_images
```
2. **Production/Deployment Environments:**
Apply the migrations safely and regenerate the client with:
```bash
bun x prisma migrate deploy
bun x prisma generate
```
---
## 2. Shared Types (`packages/types/index.ts`)
Expose these new fields in the shared interfaces `Menu` and `MenuItem` used across the backend and MCP server.
### Proposed Diff
```diff
diff --git a/packages/types/index.ts b/packages/types/index.ts
--- a/packages/types/index.ts
+++ b/packages/types/index.ts
@@ -195,6 +195,8 @@ export interface MenuItem {
label: string
url: string
order: number
+ columnImage?: string | null
+ hoverImage?: string | null
translations?: any[]
}
@@ -202,6 +204,7 @@ export interface MenuItem {
export interface Menu {
id: number
name: string
+ rootImage?: string | null
items: MenuItem[]
payload?: any
}
```
---
## 3. Backend Routes (`apps/backend/src/routes/menus.ts`)
Update the Elysia endpoints to accept, validate, process, and return the new image fields.
### Proposed Diff
```diff
diff --git a/apps/backend/src/routes/menus.ts b/apps/backend/src/routes/menus.ts
--- a/apps/backend/src/routes/menus.ts
+++ b/apps/backend/src/routes/menus.ts
@@ -32,13 +32,23 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
.post("/", async ({ body, set }) => {
try {
- const { name, items } = body as { name: string; items?: { label: string; url: string; order: number }[] };
+ const { name, rootImage, items } = body as {
+ name: string;
+ rootImage?: string | null;
+ items?: {
+ label: string;
+ url: string;
+ order: number;
+ columnImage?: string | null;
+ hoverImage?: string | null;
+ }[];
+ };
const menu = await prisma.menu.create({
data: {
name,
+ rootImage,
items: items ? {
- create: items
+ create: items.map(item => ({
+ label: item.label,
+ url: item.url,
+ order: item.order,
+ columnImage: item.columnImage,
+ hoverImage: item.hoverImage
+ }))
} : undefined
},
include: { items: true }
@@ -53,7 +63,9 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
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()
+ order: t.Number(),
+ columnImage: t.Optional(t.Nullable(t.String())),
+ hoverImage: t.Optional(t.Nullable(t.String()))
})))
})
})
.put("/:name", async ({ params: { name }, body, set }) => {
try {
- const { items, payload, isActive } = body as {
+ const { items, payload, isActive, rootImage } = body as {
payload?: any;
isActive?: boolean;
+ rootImage?: string | null;
- items: { id?: number; label: string; url: string; order: number; translations?: { langCode: string; label: string }[] }[]
+ items: {
+ id?: number;
+ label: string;
+ url: string;
+ order: number;
+ columnImage?: string | null;
+ hoverImage?: string | null;
+ translations?: { langCode: string; label: string }[]
+ }[]
};
let menu = await prisma.menu.findUnique({ where: { name } });
const updateData: any = {};
if (payload !== undefined) updateData.payload = payload;
if (isActive !== undefined) updateData.isActive = isActive;
+ if (rootImage !== undefined) updateData.rootImage = rootImage;
if (!menu) {
menu = await prisma.menu.create({ data: { name, ...updateData } });
@@ -91,3 +111,5 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
await tx.menuItem.update({
where: { id: item.id },
- data: { label: item.label, url: item.url, order: item.order }
+ data: {
+ label: item.label,
+ url: item.url,
+ order: item.order,
+ columnImage: item.columnImage,
+ hoverImage: item.hoverImage
+ }
});
if (item.translations?.length) {
@@ -105,7 +127,9 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
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 ? {
@@ -134,6 +158,7 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
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({
```
---
## 4. MCP Server Tools (`apps/mcp-server/menu-tools.ts` and `apps/mcp-server/menu-toolkit.ts`)
We will update the tool validators and schemas to allow processing these fields via MCP commands.
### 4.1 Changes to `apps/mcp-server/menu-tools.ts`
Introduce validators for `columnImage` and `hoverImage` in the `validateMenuItems` helper function.
```diff
diff --git a/apps/mcp-server/menu-tools.ts b/apps/mcp-server/menu-tools.ts
--- a/apps/mcp-server/menu-tools.ts
+++ b/apps/mcp-server/menu-tools.ts
@@ -183,6 +183,12 @@ function validateMenuItems(items: any) {
if (item.order !== undefined && typeof item.order !== "number") {
issues.push(`items[${index}].order must be a number when provided`);
}
+ if (item.columnImage !== undefined && typeof item.columnImage !== "string" && item.columnImage !== null) {
+ issues.push(`items[${index}].columnImage must be a string or null when provided`);
+ }
+ if (item.hoverImage !== undefined && typeof item.hoverImage !== "string" && item.hoverImage !== null) {
+ issues.push(`items[${index}].hoverImage must be a string or null when provided`);
+ }
```
*Note: For the structural tree validation (`validateMenuNodeData`), we can also optionally validate these keys on tree nodes if they are added as part of the JSON payload.*
### 4.2 Changes to `apps/mcp-server/menu-toolkit.ts`
Update the `upsert_menu` tool definition to support the new fields in its input schema, and pass `rootImage` correctly inside the handler.
```diff
diff --git a/apps/mcp-server/menu-toolkit.ts b/apps/mcp-server/menu-toolkit.ts
--- a/apps/mcp-server/menu-toolkit.ts
+++ b/apps/mcp-server/menu-toolkit.ts
@@ -45,6 +45,7 @@ export const MENU_TOOL_DEFINITIONS = [
isActive: { type: "boolean", description: "Optional active state for the menu" },
+ rootImage: { type: ["string", "null"], description: "Optional image URL for the root menu container" },
items: {
type: "array",
description: "Array of menu items",
@@ -56,6 +57,8 @@ export const MENU_TOOL_DEFINITIONS = [
url: { type: "string", description: "URL of the menu item" },
order: { type: "number", description: "Order sequence" },
+ columnImage: { type: ["string", "null"], description: "Optional image URL for the column" },
+ hoverImage: { type: ["string", "null"], description: "Optional image URL for hover states" },
translations: {
@@ -216,6 +219,12 @@ export async function handleMenuTool(api: AxiosInstance, name: string, params: a
if (itemCheck.issues.length > 0) {
throw new Error(`Invalid menu items: ${itemCheck.issues.join("; ")}`);
}
+ if (params.rootImage !== undefined) {
+ body.rootImage = params.rootImage;
+ } else if (existingMenu?.rootImage !== undefined) {
+ body.rootImage = existingMenu.rootImage;
+ }
+
if (params.payload !== undefined) {
body.payload = params.payload;
} else if (existingMenu?.payload !== undefined) {
@@ -275,6 +284,7 @@ export async function handleMenuTool(api: AxiosInstance, name: string, params: a
name: res.data.name,
isActive: res.data.isActive,
+ rootImage: res.data.rootImage,
itemCount: res.data.items?.length || 0,
hasPayload: !!res.data.payload
```
---
## 5. Potential Risks & Mitigation
1. **Database Upgrades / Existing Records:**
- **Risk:** Existing database records will have `NULL` values for the new columns.
- **Mitigation:** Since the fields are optional/nullable in Prisma (`String?`) and shared types (`string | null`), this is safe and backwards-compatible. No database seed adjustments are strictly required to avoid crash, but seeding mock images is recommended.
2. **Frontend Type Mismatch:**
- **Risk:** Client apps (SPA/Website) define local structures for `MegaMenuPayload` which might need updating once these fields start appearing in API responses.
- **Mitigation:** The changes do not break old rendering logic as they are optional fields, but they must be exposed on the frontend interfaces in Milestone 3 to be fully utilized.
3. **Regenerating Client Libraries:**
- **Risk:** Developers or test pipelines compiling without running `prisma generate` will run into compilation errors due to type mismatches.
- **Mitigation:** Running `npm run build` or `bun run build` in the monorepo automatically triggers prisma client generation.
+150
View File
@@ -0,0 +1,150 @@
# Handoff Report — Explorer 2 (Milestone 2: Backend & DB)
## 1. Observation
We observed the following definitions and implementations in the codebase:
1. **Prisma Schema (`apps/backend/prisma/schema.prisma`)**:
- `Menu` model (lines 638-646):
```prisma
model Menu {
id Int @id @default(autoincrement())
name String @unique // e.g., "header", "footer"
payload Json?
isActive Boolean @default(true)
items MenuItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
- `MenuItem` model (lines 648-658):
```prisma
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)
translations MenuItemTranslation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
2. **Shared Types (`packages/types/index.ts`)**:
- Interfaces `MenuItem` and `Menu` (lines 195-208):
```typescript
export interface MenuItem {
id: number
label: string
url: string
order: number
translations?: any[]
}
export interface Menu {
id: number
name: string
items: MenuItem[]
payload?: any
}
```
3. **Backend Routes (`apps/backend/src/routes/menus.ts`)**:
- Validation schema of `POST /` body (lines 50-59):
```typescript
body: t.Object({
name: t.String(),
items: t.Optional(t.Array(t.Object({
label: t.String(),
url: t.String(),
order: t.Number()
})))
})
```
- Validation schema of `PUT /:name` body (lines 132-147):
```typescript
body: t.Object({
payload: t.Optional(t.Any()),
isActive: t.Optional(t.Boolean()),
items: t.Array(t.Object({
id: t.Optional(t.Number()),
label: t.String(),
url: t.String(),
order: t.Number(),
translations: t.Optional(t.Array(t.Object({
langCode: t.String(),
label: t.String()
})))
}))
})
```
- Database operations mapping in `PUT /:name` (lines 90-118):
```typescript
if (item.id && existingItemIds.includes(item.id)) {
await tx.menuItem.update({
where: { id: item.id },
data: { label: item.label, url: item.url, order: item.order }
});
// ...
} else {
await tx.menuItem.create({
data: {
menuId: menu!.id,
label: item.label,
url: item.url,
order: item.order,
translations: item.translations?.length ? { ... } : undefined
}
});
}
```
4. **MCP Tools (`apps/mcp-server/menu-tools.ts` and `apps/mcp-server/menu-toolkit.ts`)**:
- `validateMenuItems` helper (lines 158-184):
```typescript
if (item.order !== undefined && typeof item.order !== "number") {
issues.push(`items[${index}].order must be a number when provided`);
}
```
- `upsert_menu` tool definition input schema (lines 34-74):
```json
items: {
type: "array",
description: "Array of menu items",
items: {
type: "object",
properties: {
id: { type: "number", description: "ID of existing item (optional)" },
label: { type: "string", description: "Label of the menu item" },
url: { type: "string", description: "URL of the menu item" },
order: { type: "number", description: "Order sequence" },
translations: { ... }
},
required: ["label", "url", "order"]
}
}
```
## 2. Logic Chain
- Adding the new columns to Prisma schema (Observation 1) as optional fields (`rootImage String?`, `columnImage String?`, `hoverImage String?`) ensures database schema compatibility. Running `prisma migrate dev` compiles a safe SQL statement that populates default `NULL` values for existing rows, which avoids downtime or data loss.
- Exposing the fields on the shared interfaces (`packages/types/index.ts`, Observation 2) aligns the TypeScript contracts between package users.
- In order to save these image fields, we must extend Elysia's schema validator body tags `t.Optional(t.Nullable(t.String()))` (Observation 3) and map the properties directly to the Prisma create/update calls, otherwise Elysia will strip them and Prisma won't save them.
- Finally, updating `validateMenuItems` (Observation 4) and the JSON inputSchema of `upsert_menu` (Observation 4) allows external agents/clients to supply the image variables when working via MCP server endpoints.
## 3. Caveats
- We assumed frontend (SPA/Website) changes are scheduled under Milestone 3 and thus out of scope for implementation/updates in this backend-only task, though we identified their locations (`apps/spa/src/store/menuStore.ts` and `apps/website/src/components/navigation/menuTypes.ts`).
- We did not apply database changes or make code modifications as we are under a read-only constraint.
## 4. Conclusion
We have mapped the precise locations, code structures, and type changes required to fully implement `rootImage`, `columnImage`, and `hoverImage` across the database, shared types, backend controllers, and MCP tool schemas.
Detailed changes are saved to `/home/ti/git/homaroma/.agents/explorer_m2_2/analysis.md`.
## 5. Verification Method
To verify the implementation once the implementer carries out the changes:
1. Run the build/check scripts to ensure zero compilation or type errors:
```bash
npm run build
```
2. Test menu endpoint updates by sending a HTTP PUT to `/api/menus/header` or calling the MCP tool `upsert_menu` with image URLs, then verify the response returns the fields and database contains the updated columns.
+6
View File
@@ -0,0 +1,6 @@
# Progress
- Last visited: 2026-06-28T21:20:34-04:00
- Initialized Explorer 2.
- Created ORIGINAL_REQUEST.md and BRIEFING.md.
- Next: Read skill files and start investigating files requested in prompt.