Files
editor/packages/mcp/test-reports/t3-scenario/run-summary.json
T
Adrian PerezandClaude Opus 4.7 e8d0b13ff5 feat(mcp,editor): Option A+B storage + 10 agent deliverables (Phase 7)
Ships the combined filesystem/Supabase storage adapter + MCP scene
lifecycle tools + Next.js API routes + editor /scene/[id] route, so
an MCP save is directly openable at /scene/<id> without any
injection hack. End-to-end verified: 10/10 e2e steps pass.

Storage (A1/A2/A3):
- SceneStore interface + error classes + slug helpers
- FilesystemSceneStore at $PASCAL_DATA_DIR (defaults XDG/~/.pascal)
  with atomic writes, .index sidecar, optimistic locking
- SupabaseSceneStore with scenes + scene_revisions tables, RLS
  migration SQL, mock-backed unit tests
- createSceneStore(env) auto-selects based on SUPABASE_URL +
  SUPABASE_SERVICE_ROLE_KEY

MCP tools (A4, A8, A9, A10):
- save_scene / load_scene / list_scenes / delete_scene / rename_scene
- list_templates / create_from_template (3 seed templates:
  empty-studio, two-bedroom, garden-house)
- generate_variants (7 mutation kinds, seeded RNG, save=true|false)
- photo_to_scene (vision sampling → scene graph → save)

Editor (A5, A6):
- /api/scenes + /api/scenes/[id] with RFC 7232 If-Match locking
- /scene/[id] and /scenes route pages with save button, SceneLoader
- Removed the window.__pascalScene dev injection hack

Security + UX edges (A7, A8):
- AssetUrl Zod validator: asset:// blob: data:image/ /path https:
  (http://localhost for dev) + PASCAL_ALLOWED_ASSET_ORIGINS env
  allowlist. Hardens scan.url, guide.url, item.asset.src,
  material.texture.url, MaterialMaps.*Map
- Auto-frame camera on empty→non-empty scene transition
  (camera-controls:fit-scene emitter event)

Shared utilities:
- rehydrateSiteChildren() extracted to packages/mcp/src/lib/ and
  used by both create-from-template and generate-variants to work
  around the SiteNode.children-as-objects vs. ids inconsistency
  (CROSS_CUTTING §2)
- Storage + MCP subpath exports added to packages/mcp/package.json
  (CROSS_CUTTING §4)

Tests: 293 pass / 0 fail across 40 files (was 142 pre-Phase-7).
Biome: clean.

Phase-7 e2e script at packages/mcp/test-reports/phase7-e2e.ts:
MCP HTTP + editor Next.js both point at $PASCAL_DATA_DIR =
/tmp/pascal-e2e, save_scene from MCP, GET /api/scenes/<id> from
editor server, /scenes list page renders all saved scenes, scene
page renders SceneLoader, delete_scene works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:29:28 +02:00

166 lines
4.1 KiB
JSON

{
"transport": {
"kind": "http",
"note": "shared HTTP server"
},
"steps": [
{
"n": 1,
"name": "discover",
"ok": true,
"durationMs": 4,
"summary": "building=building_bfqg91ai9ijps9ej, level=level_wyuoxj87czq3v0re (of 1 buildings, 1 levels)",
"nodeIds": ["building_bfqg91ai9ijps9ej", "level_wyuoxj87czq3v0re"]
},
{
"n": 2,
"name": "perimeter walls",
"ok": true,
"durationMs": 2,
"summary": "created 4 perimeter walls (result.createdIds=[\"wall_y87bsrljd2245n51\",\"wall_sja6jpda73tlhxwb\",\"wall_aegff27krjwgmkmi\",\"wall_dcymglyle9ff09ti\"])",
"nodeIds": [
"wall_y87bsrljd2245n51",
"wall_sja6jpda73tlhxwb",
"wall_aegff27krjwgmkmi",
"wall_dcymglyle9ff09ti"
]
},
{
"n": 3,
"name": "interior partitions",
"ok": true,
"durationMs": 1,
"summary": "created 7 interior walls",
"nodeIds": [
"wall_rl83cc5tnbf4b34j",
"wall_qv53jm9kvl7k6slf",
"wall_8y52fwzco2ep7fb1",
"wall_hrfixeusz7zb7x63",
"wall_1ullk9bm6dw15i9t",
"wall_c4fjjswnk0mprctm",
"wall_p359pffjf3qs59cy"
]
},
{
"n": 4,
"name": "set zones",
"ok": true,
"durationMs": 3,
"summary": "created 4 zones: bedroom-1, bedroom-2, bathroom, living-kitchen",
"nodeIds": [
"zone_3fyksm10tb0dhn1e",
"zone_u95l1bt35jci3gvu",
"zone_r9ma8tvsqt9w1zey",
"zone_l189q61kf9ra2m8t"
]
},
{
"n": 5,
"name": "cut openings",
"ok": true,
"durationMs": 6,
"summary": "3 doors, 3 windows",
"nodeIds": [
"door_cjzja4lt8owg88wg",
"door_bs7bf0azevq9vd76",
"door_o8etwqsemfgj5mkj",
"window_47x40mtv2l4ca9p4",
"window_n09awmg5m3ct4fvn",
"window_xlta3f3f0cnmbti3"
]
},
{
"n": 6,
"name": "validate scene",
"ok": true,
"durationMs": 1,
"summary": "valid=true, errors=0"
},
{
"n": 7,
"name": "measure furthest zones",
"ok": true,
"durationMs": 3,
"summary": "furthest: zone_3fyksm10tb0dhn1e <-> zone_u95l1bt35jci3gvu = 7.000m",
"nodeIds": ["zone_3fyksm10tb0dhn1e", "zone_u95l1bt35jci3gvu"]
},
{
"n": 8,
"name": "export json",
"ok": true,
"durationMs": 1,
"summary": "exported 15392 bytes -> apartment.json"
},
{
"n": 9,
"name": "undo 3 steps",
"ok": true,
"durationMs": 2,
"summary": "undone=3, nodes 24 -> 21 (delta=3)"
},
{
"n": 10,
"name": "redo 3 steps",
"ok": true,
"durationMs": 2,
"summary": "redone=3, nodes 21 -> 24"
},
{
"n": 11,
"name": "duplicate level + validate",
"ok": true,
"durationMs": 2,
"summary": "newLevelId=level_cxvltlqvgqcasiep, cloned=22, valid=true, errors=0",
"nodeIds": ["level_cxvltlqvgqcasiep"]
},
{
"n": 12,
"name": "delete duplicated level",
"ok": true,
"durationMs": 3,
"summary": "deleted 22 nodes; nodes 46 -> 24",
"nodeIds": [
"level_cxvltlqvgqcasiep",
"wall_xhn7o9bfpmcv2znr",
"window_0qb4bgvzp46y412o",
"window_cjk6a6zwsn48dj5u",
"wall_hgatfahp4i53s139",
"wall_fp25ulcwqmsim8p5",
"window_jv0g8uos313ljbbe",
"wall_mjue0xaodm8d3vzi",
"wall_brw2c9vg502md7a0",
"wall_1at54a0mfxgq2txb",
"door_l8k2adas2djwpcf8",
"wall_6m920gigvtn113af",
"wall_2hazwrgv922l3t6q",
"door_a62eecbdzpdoqnlo",
"wall_11h43exay41fub7f",
"door_06dpidftldcoaisv",
"wall_u7e1gp0xbir5112y",
"wall_jbwbwvgt9lzw51mk",
"zone_hkp5wsyw7aydi175",
"zone_rpqx5ul6bl1tpdmd",
"zone_kohgjo47reiluhto",
"zone_o65jm5oy5v5ej6aw"
]
}
],
"final": {
"totalNodes": 24,
"zones": 4,
"doors": 3,
"windows": 3,
"step5NodeCount": 24,
"validationSummary": {
"valid": true,
"errors": []
},
"undoObservation": {
"undone": 3,
"before": 24,
"after": 21,
"delta": 3
}
}
}