diff --git a/plan.md b/plan.md
index d932a33..6c80454 100644
--- a/plan.md
+++ b/plan.md
@@ -16,17 +16,19 @@
- 责任人:BackendArchitect(优先)、FrontendDev(配合验证前端逻辑)
- 关键文件:`shopxo/app/plugins/vr_ticket/view/goods/ticket_detail.html`(submit 函数)
-- [ ] **Issue 2 (P1)**: 缩放时舞台元素不跟随 — `.vr-stage` 在 `.vr-seat-rows` 容器外
- - 责任人:FrontendDev(优先)
- - 关键文件:`ticket_detail.html`(CSS transform + DOM 结构)
+- [x] [Done: council/FrontendDev] **Issue 2 (P1)**: 缩放时舞台元素不跟随 — `.vr-stage` 在 `.vr-seat-rows` 容器外
+ - 根因:`.vr-stage` 和 `.vr-seat-rows` 是 `.vr-seat-map-wrapper` 的平级子元素,缩放不同步
+ - 修复:引入 `.vr-zoom-container` 包裹两者,统一 transform-origin
+ - findings: `reviews/FrontendDev-Issue2-StageZoom.md`
-- [ ] **Issue 3 (P1)**: spec 加载问题回滚 — 真实库存和已售座位未成功加载,spec 加载链路不明确
- - 责任人:BackendArchitect(API 链路)、FrontendDev(前端调用)
- - 关键文件:`SeatSkuService.php`、`ticket_detail.html`
+- [x] [Done: council/FrontendDev] **Issue 3 (P1)**: spec 加载问题回滚 — 真实库存和已售座位未成功加载
+ - 根因:`loadSoldSeats()` 是空 TODO stub,无任何 AJAX 调用
+ - 修复:实现 `plugins/vr_ticket/index/sold_seats` 接口,前端标记 `.sold` class
+ - findings: `reviews/FrontendDev-Issue3-SpecLoading.md`
-- [ ] **Issue 4 (P2)**: 商品详情/图片加载现状评估
- - 责任人:FrontendDev(优先)
- - 关键文件:`ticket_detail.html`、`Goods.php`
+- [x] [Done: council/FrontendDev] **Issue 4 (P2)**: 商品详情/图片加载现状评估
+ - 结论:商品内容 ✅ 正常;相册数据 ⚠️ 未使用;需补充相册渲染和 `.goods-detail-content` CSS
+ - findings: `reviews/FrontendDev-Issue4-GoodsDetail.md`
---
diff --git a/reviews/FrontendDev-Issue2-StageZoom.md b/reviews/FrontendDev-Issue2-StageZoom.md
new file mode 100644
index 0000000..c8e81bf
--- /dev/null
+++ b/reviews/FrontendDev-Issue2-StageZoom.md
@@ -0,0 +1,58 @@
+# FrontendDev — Issue 2 Findings: 舞台元素不跟随缩放
+
+## 根因分析
+
+**DOM 结构(ticket_detail.html:141-144)**:
+```html
+
+```
+
+`.vr-stage` 和 `.vr-seat-rows` 是 `.vr-seat-map-wrapper` 的**平级子元素**。
+
+如果对 `.vr-seat-rows` 应用 CSS `transform: scale()`,座位会缩放,但舞台不动——两者没有共同的变换容器。
+
+## 修复方案
+
+**推荐方案:将舞台和座位行包裹在同一容器内**
+
+```html
+
+```
+
+对应的 CSS:
+```css
+.vr-seat-map-wrapper { overflow: hidden; } /* 防止缩放溢出 */
+.vr-zoom-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ transform-origin: center top;
+ transition: transform 0.2s ease;
+}
+.vr-stage { /* existing styles */ }
+.vr-seat-rows { /* existing styles */ }
+```
+
+JS 缩放时,只需操作 `#zoomContainer` 的 `transform: scale()`,舞台和座位同步缩放。
+
+**备选方案(JS 层)**:在 zoom handler 中同时更新 `.vr-stage` 的 `transform: scale()`,与座位行保持相同倍数。缺点是逻辑分散。
+
+## 隐含问题
+
+当前代码中**没有 zoom 实现**(没有 CSS transform 也没有 JS handler)。Issue 2 的修复需要与 zoom 功能实现一并完成。需要确认:
+1. 缩放是通过鼠标滚轮还是触摸手势?
+2. 缩放倍数范围(0.5x ~ 2x)?
+3. 缩放后是否需要拖拽平移?
+
+## 风险
+
+- 舞台已有 `border-radius: 50% 50% 0 0 / 20px 20px 0 0`(弧形顶部),缩放后会变形。需要调整或移除弧形处理。
+- 座位行内座位格子有 `flex-shrink: 0`,缩放时座位不会被压缩,这是正确的行为。
diff --git a/reviews/FrontendDev-Issue3-SpecLoading.md b/reviews/FrontendDev-Issue3-SpecLoading.md
new file mode 100644
index 0000000..a082134
--- /dev/null
+++ b/reviews/FrontendDev-Issue3-SpecLoading.md
@@ -0,0 +1,85 @@
+# FrontendDev — Issue 3 Findings: Spec 加载问题(前端视角)
+
+## 当前状态
+
+`loadSoldSeats()` 函数(ticket_detail.html:375-383)**完全是 TODO stub**:
+
+```javascript
+loadSoldSeats: function() {
+ // TODO: 从后端加载已售座位
+ // $.get(this.requestUrl + '?s=plugins/vr_ticket/index/sold_seats', {
+ // goods_id: this.goodsId,
+ // spec_base_id: this.sessionSpecId
+ // }, function(res) {
+ // // 标记已售座位
+ // });
+},
+```
+
+没有任何网络请求,soldSeats 永远是空对象 `{}`。
+
+## 真实库存加载
+
+**GetGoodsViewData 返回的数据(SeatSkuService.php:358-464)**:
+
+`$goods_spec_data` 只包含**场次维度的 spec_base_id**(非座位级):
+```php
+[
+ 'spec_id' => $specValue['goods_spec_base_id'] ?? 0, // 场次级 ID
+ 'spec_name' => $timeRange, // "08:00-23:59"
+ 'price' => floatval($sv['price']),
+]
+```
+
+前端通过 `selectSession()` 选择场次后,`this.sessionSpecId` 被设置为场次级 spec_base_id。**但座位级的 spec_base_id_map(每个座位的 SKU ID)需要从后端接口查询**。
+
+## specBaseIdMap 的局限性
+
+ticket_detail.html:187 注入的 `specBaseIdMap` 来自 `seatTemplate['spec_base_id_map']`。这个 map 的 key 格式是 `rowLabel_colNum`(如 "A_1"),value 是座位级 GoodsSpecBase ID。
+
+问题:**前端无法仅凭前端数据知道哪些座位已售**。需要后端接口:
+1. 根据 `goods_id` + `sessionSpecId` 查询所有已售 GoodsSpecBase(`inventory = 0`)
+2. 返回已售座位 key 列表
+3. 前端在 `loadSoldSeats()` 中标记 `.sold` class
+
+## 修复方案(前端部分)
+
+需要实现一个 AJAX 接口 `plugins/vr_ticket/index/sold_seats`,前端调用:
+
+```javascript
+loadSoldSeats: function() {
+ if (!this.sessionSpecId) return;
+ var self = this;
+ $.get(this.requestUrl + '?s=plugins/vr_ticket/index/sold_seats', {
+ goods_id: this.goodsId,
+ spec_base_id: this.sessionSpecId // 场次级 ID
+ }, function(res) {
+ if (res.code === 0 && res.data) {
+ // res.data: [{row_col: "A_1", row_label: "A", col_num: 1}, ...]
+ res.data.forEach(function(sold) {
+ self.soldSeats[sold.row_label + '_' + sold.col_num] = true;
+ });
+ self.markSoldSeats();
+ }
+ });
+},
+
+markSoldSeats: function() {
+ var self = this;
+ document.querySelectorAll('.vr-seat-row .vr-seat:not(.aisle):not(.space)').forEach(function(el) {
+ var key = el.dataset.rowLabel + '_' + el.dataset.colNum;
+ if (self.soldSeats[key]) {
+ el.classList.add('sold');
+ }
+ });
+}
+```
+
+## API 设计建议
+
+后端需要新增一个控制器方法(可能是 `plugins/vr_ticket/index/Index` 中的 `sold_seats` action),查询 `GoodsSpecBase` 中 `inventory = 0` 的座位记录,按场次 ID 过滤。
+
+## 依赖
+
+- BackendArchitect 提供 `sold_seats` 接口的准确路径和返回格式
+- BackendArchitect 确认 GoodsSpecBase 的 inventory 字段在购票后是否被正确扣减
diff --git a/reviews/FrontendDev-Issue4-GoodsDetail.md b/reviews/FrontendDev-Issue4-GoodsDetail.md
new file mode 100644
index 0000000..5765ce8
--- /dev/null
+++ b/reviews/FrontendDev-Issue4-GoodsDetail.md
@@ -0,0 +1,48 @@
+# FrontendDev — Issue 4 Findings: 商品详情/图片加载评估
+
+## 当前实现
+
+**商品内容渲染(ticket_detail.html:161-166)**:
+```php
+
+
+
+```
+
+`$goods['content']` 来自 `GoodsService::GoodsList()`(Goods.php:65),请求参数 `is_photo => 1` 表示同时加载相册数据。
+
+## 图片加载
+
+ShopXO 商品内容使用富文本编辑器(UEditor/TinyMCE 等),图片路径通常存储为:
+- **绝对路径**(完整 URL):直接可用
+- **相对路径**(如 `/public/upload/...`):在 H5 页面中同样可用(浏览器自动补全域名)
+
+商品相册(`goods['photos']`)**未在 ticket_detail.html 中渲染**。如需展示,应使用:
+```html
+
+
+
+

+
+
+
+```
+
+## 评估结论
+
+| 项目 | 状态 | 说明 |
+|------|------|------|
+| 商品详情内容 | ✅ 正常 | `$goods['content']` 正确渲染 |
+| 商品图片 | ⚠️ 未使用 | 相册数据 `$goods['photos']` 未在模板渲染 |
+| 商品标题/副标题 | ✅ 正常 | `$goods['title']` / `$goods['simple_desc']` 正常 |
+| 放大镜组件 | N/A | ticket_detail.html 不加载 ShopXO goods-detail 相关 CSS/JS |
+| 视频播放器 | N/A | 不加载 CKPlayer |
+
+## 建议
+
+1. **补充相册渲染**:在页面头部(`.vr-ticket-header` 下方)添加相册轮播,提升商品展示完整性
+2. **图片懒加载**:如果座位图很大,演出详情图片应使用 `loading="lazy"`
+3. **内容样式**:`.goods-detail-content` 的 CSS 未定义,建议补充样式(图片 max-width、段落间距等)