Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/BOARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
| **SB-2 脚本导入对话框 + 批量建链(M4 E1;E1 整条链自此贯通)**:`storyboard/providers/script_import_controller.dart` + `widgets/script_import_dialog.dart`——粘贴框/策略切换/实时预览 → 确认后 N 个 shot 节点 + N-1 条 narrative 边收进**单个事务**。**失败必须零残留**:半条链(几个建好的散镜 + 几条边)比一个错误提示难收拾得多——用户既不知道哪几镜落地了也没有一键撤销;测试用的 fake UoW **真回滚**(写入先落 staging,闭包整体成功才 commit),真事务语义仍由 `transaction_integration_test`(真 PG)兜底。两处坑:①**不走 CanvasNodesController**(它一次只建一个节点、不入事务)——直接经 scope 写库,落地后 invalidate 节点与边两个控制器 ②**provider 不加 autoDispose**——调用方只 `ref.read` 不订阅,autoDispose 会在 read 后立刻销毁 ref,事务 await 完再 invalidate 就撞上已销毁的 ref,画布刷不出新链。落点=第一镜落视口中心,后续每 `kShotChainSpacingX`(260) 沿 x 排开;入口=FAB 菜单「导入脚本…」+ 空态 CTA。ARB +13 键。+18 例 | #225 |
| **XM-2 图片元数据(M4;补齐 XM-1 的图片侧)**:`core/media/png_dimensions.dart` 纯函数(PNG 签名 + IHDR,不引解码库)→ 四个落盘点(inline 单/批、remote 单/批)顺手解析宽高,连同 `task.seed` 写进 `node.type_config`(主图那一张,与 XM-1 视频侧同键)与 `batch_results.width/height/seed`(逐 slot 各记各的,schema 已建未用的列自此有值)。**判定刻意严格、拿不到就不写键**:认错了会把垃圾尺寸写进库再一路带到排版,而列停在 NULL 画廊才知道退回默认比例——每条正向断言都配一条「非 PNG 时这些键根本不出现」的反向断言。inline 路径 bytes 在手零额外 IO;remote 路径回读文件头 33 字节(完整 IHDR 含 CRC),**任何探针失败一律吞掉**——元数据是锦上添花,绝不能把一次成功的生成拖成失败。视频侧不动(宽高仍归 XM-1 抽帧探针)。+22 例 | #226 |
| LB-23 内存基线:cacheWidth 缩略解码收口 ×4 文件 5 站点(gallery tile 图片+视频缩略图/batch grid/两微缩略图;lightbox InteractiveViewer 有意豁免)+ ImageCache 上限 100→256MB(`kImageCacheMaxBytes`,main bootstrap 设定)+ perf-baseline 内存水位节(方法论/阈值/mac 空载实测 140MB,画廊与生成场景按 SOP 待填,Windows 列待群内机器)+ keepAlive 盘点 8 文件无未管控大对象 | #227 |
| CH-2 视频 Inspector 角色区:角色区/命名框/角色 chip 抽共享 `characters_section.dart`(image inspector 1033→672 行,行为零变化以既有角色区测试一行未改全绿为证)+ video inspector 门控挂载(`maxRefImages>0` 对齐 CH-1 注入门,不检查 imageToImage——r2v/omni 语义;与 image 侧常挂+警示有意不同,视频多数 provider 无 ref 能力常挂=一屏死区)——CH-1 视频角色注入自此有用户可见入口 | #230 |

## M1 补遗(审计发现的悬空项)

Expand Down
2 changes: 1 addition & 1 deletion docs/MASTERPLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ M6 「公开上线」……… 官网 + 示例项目 + 冷启动执行(HN/Reddit
| E2 画廊 | 视频缩略/播/时长(**GA-1/2/7 ✅**);筛选(**GA-3 ✅**);存角色(**GA-4 ✅**——均 2026-08-05 Polish Wave 1);发送画布(GA-5,D-M4-2);删除(GA-6,D-M4-3,文件+DB 同步收敛) | GA-1~7 |
| E3 聚合器 | 设置页可视化 CRUD+key 校验;≥2 协议模板;(二期)新增热生效 | AG-1~5 |
| E4 导出 | narrative 自动排序(主体已随 PR #143 合入 main;剩 EX-1′)+转码归一+进度取消(**EX-3 ✅ 2026-08-05 Polish Wave 1**:ProcessStarter 流式通道+determinate 进度+取消,顺带债144 两件) | EX-1′~3 |
| E5 角色进阶 | 视频生成自动带角色图(**CH-1 注入链路 ✅ 2026-08-05 Polish Wave 1**——注:video inspector 尚无角色区,character_ids 当前仅 image inspector 可写/导入可带,用户可见入口随 CH-2);video inspector 角色区;角色库管理页 | CH-1~3 |
| E5 角色进阶 | 视频生成自动带角色图(**CH-1 注入链路 ✅ 2026-08-05 Polish Wave 1**——用户可见入口已随 CH-2 落地);video inspector 角色区(**CH-2 ✅ #230**);角色库管理页 | CH-1~3 |
| E6 项目复制 | ⚠️ **裁决:排上线后**(BOARD 原判"单独立项";≡ backend BP-11,复用 LB-12 导入机器后
成本减半)。features 的 PD-1~3 保留为**实现细化参考**,不进 M4 波次;若用户要提前,依赖 LB-12 | PD-1~3(细化) |
| E7 跨模块 metadata | duration/width/height/seed 回填 result 与 batch slot(**多 epic 阻塞点**) | XM-1~2 |
Expand Down
4 changes: 4 additions & 0 deletions docs/superpowers/plans/2026-07-07-launch-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ narrative 链可序列播放且与导出同序。
使用——需决策:留原文件共享 or 一并抽公共 widget;这不是零决策机械搬移**;
video 侧挂 NodeInputsSection 前,门控 maxRefImages>0;顺带给 1019 行的 image inspector 减重
(勿与 P1-17 _PromptPreview 债同窗)。
状态:已随 #230 落地——三类抽共享 characters_section.dart(NameDialog/CharacterChip
一并转公开,_PresetsSection 改 import;image inspector 1033→672 行零行为变化,
既有角色区测试一行未改全绿);video 侧 maxRefImages>0 门控挂载,
requireImageToImageMode=false 对齐 CH-1 注入口径(不检查 modes)。
- **CH-3 角色库管理页**(M/L,可并行;入口形态 D-M4-8 默认 A 可先行):新 `features/characters/`
整屏(路由完全仿 gallery 的 currentGalleryProjectProvider 模式,优先级 canvas>gallery>
characters>screen);列表卡(缩略图/名称/描述/图数);改名/删除用现有 controller 方法;
Expand Down
217 changes: 217 additions & 0 deletions docs/superpowers/plans/2026-08-20-ch-2-video-inspector-characters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# CH-2 视频 Inspector 角色区 — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 把 image inspector 的角色区抽成共享 widget,挂进 video inspector——给 CH-1 已落地的视频角色注入补上用户可见入口(video 节点自此可写 `type_config.character_ids`)。

**Architecture:** 机械抽取 + 参数化门控。`_CharactersSection`/`_NameDialog`/`_CharacterChip` 从 `image_config_inspector.dart`(1033 行)整体搬到新共享文件 `characters_section.dart` 并转公开;唯一行为参数是 ref 支持判定——image 保持 `maxRefImages>0 && imageToImage`,video 对齐 CH-1 注入门只要 `maxRefImages>0`(**不检查 modes**,r2v/omni 语义,见 generation_controller.dart:674 拍板注释)。video 侧按卡片要求以 `maxRefImages>0` 门控挂载(不支持的 provider 整段不出现,与 image 侧「挂载+警示文案」不同属有意差异)。

**决策(卡片留白处,本计划拍板):** `_NameDialog` 与 `_CharacterChip` 同时被 `_PresetsSection` 使用——**一并搬进共享文件转公开**(`InspectorNameDialog`/`CharacterChip`),image inspector 回头 import。理由:留私有副本=两份漂移源;`characters_section.dart` 是它们语义上的家(chip 渲染角色、dialog 命名角色,`_PresetsSection` 只是复用者)。image inspector 从 1033 行减 ~340 行,顺带完成卡片「给 image inspector 减重」目标。**不动 `_PromptPreview`**(P1-17 债明令勿同窗)。

**Tech Stack:** 纯 widget 搬移(零新依赖);InspectorSubmitController.saveConfig 写 `character_ids`(既有);测试复用 `test_app.dart` pumpInkApp + fake_repositories/fake_character harness。**零新 ARB key**(全部复用 inspectorCharacters* 既有键,本卡不碰 l10n)。

**行为零变化的证据面:** 既有 `image_config_inspector_characters_test.dart`(10 例)不改一行断言、搬移后必须全绿——这是「机械搬移未走样」的红线证据。

---

### Task 1: 抽取共享 `characters_section.dart`(image 侧行为零变化)

**Files:**
- Create: `lib/features/canvas/widgets/characters_section.dart`
- Modify: `lib/features/canvas/widgets/image_config_inspector.dart`(删 L586-940 三个类,import 新文件,改 4 处调用点)
- Test: 既有 `test/features/canvas/widgets/image_config_inspector_characters_test.dart` **不改动**,作回归证据

- [ ] **Step 1: 新建 `characters_section.dart`,整体搬移三个类**

从 `image_config_inspector.dart` 剪切(非复制)以下内容到新文件:
- `_CharactersSection` + `_CharactersSectionState`(现 L586-817)→ 改名 `CharactersSection`/`_CharactersSectionState`
- `_NameDialog` + `_NameDialogState`(现 L819-867 一带,含「controller 生命周期归 dialog」注释)→ 改名 `InspectorNameDialog`/`_InspectorNameDialogState`
- `_CharacterChip`(现 L869-940 一带,含 LB-23 cacheWidth 与 `_kCharacterThumbSize` 常量)→ 改名 `CharacterChip`;`_kCharacterThumbSize = 20` 常量一起搬

新文件头注释与公开签名(构造参数新增 `requireImageToImageMode`):

```dart
// CharactersSection:config 节点角色区(CH-2 起 image/video inspector 共享)。
// 把项目级角色挂到节点(写 type_config.character_ids)+ 存为角色/从文件导入。
// ref 支持判定按调用方口径参数化:image 要求 maxRefImages>0 且 imageToImage;
// video 对齐 CH-1 注入门只要 maxRefImages>0(不检查 modes——r2v/omni 的参考图
// 语义不落在 modes 里,见 generation_controller._injectCharacterRefs 拍板注释)。

class CharactersSection extends ConsumerStatefulWidget {
const CharactersSection({
super.key,
required this.targetNode,
required this.selectedCaps,
required this.requireImageToImageMode,
});

final CanvasNode targetNode;
final ProviderCapabilities? selectedCaps;

/// image inspector 传 true(维持原门);video inspector 传 false(CH-1 口径)。
final bool requireImageToImageMode;
...
}
```

`_supportsRefs` 改为:

```dart
bool get _supportsRefs {
final caps = widget.selectedCaps;
if (caps == null || caps.maxRefImages <= 0) return false;
return !widget.requireImageToImageMode ||
caps.modes.contains(GenerationMode.imageToImage);
}
```

其余方法体(`_readAttached`/`_toggle`/`_characterThumb`/`_referenceSource`/`build`/`_importFromFile`/`_createFromReference`/`_promptName`)**逐字搬移不改**。import 列表按 analyzer 报错补齐(来源即 image_config_inspector 现 import:canvas_node/provider_capabilities/inspector_submit_controller/characters_controller/canvas_edges/nodes controller/character_assets/file_resolver/ink_error/l10n_x/app_theme/tokens/ink_error_banner/ink_dashed_slot/file_selector 等)。

- [ ] **Step 2: image_config_inspector.dart 改调用点**

```dart
import 'characters_section.dart';
```

四处改名:
1. L442 挂载处:`_CharactersSection(` → `CharactersSection(targetNode: ..., selectedCaps: ..., requireImageToImageMode: true,)`
2. `_PresetsSection` 内 L531:`_CharacterChip(` → `CharacterChip(`
3. `_PresetsSection` 内 L577:`_NameDialog(` → `InspectorNameDialog(`
4. 文件头注释若列了区块清单,同步措辞(角色区已抽共享文件)

- [ ] **Step 3: 回归验证(既有 10 例一行未改必须全绿)**

```bash
flutter analyze lib test 2>&1 | tail -1
flutter test test/features/canvas/widgets/image_config_inspector_characters_test.dart 2>&1 | tail -2
```

Expected: No issues / All tests passed(若测试红=搬移走样,回 Step 1 对照原文逐字核对,禁止改测试迁就实现)。

- [ ] **Step 4: 提交**

```bash
git add lib/features/canvas/widgets
git commit -m "refactor(canvas): CH-2 前置——角色区/命名框/角色 chip 抽共享 characters_section.dart,image inspector 减重 ~340 行行为零变化

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"
```

### Task 2: video inspector 挂载角色区(TDD)

**Files:**
- Modify: `lib/features/canvas/widgets/video_config_inspector.dart`(NodeInputsSection 挂载点前,现 L305 一带)
- Test: `test/features/canvas/widgets/video_config_inspector_characters_test.dart`(新建)

- [ ] **Step 1: 写失败测试**

新建测试文件,harness 三来源拼装:caps 常量抄 `video_config_inspector_test.dart` 的 `_fakeVideoCaps`(maxRefImages=1,即 ref-capable)与 `_noCameraCaps`(maxRefImages=0);角色数据/仓储 override 抄 `image_config_inspector_characters_test.dart` 的 fake_character + fake_repositories 用法;节点必须带 `projectId`/`canvasId`(角色区首行守卫 projectId==null 即 shrink——image 侧测试文件头注释已踩过此坑)。

```dart
// CH-2:video inspector 角色区——maxRefImages>0 挂载(不要求 imageToImage,CH-1 口径),
// =0 整段不出现;chip 点选写 type_config.character_ids。
void main() {
testWidgets('maxRefImages>0 → 角色区出现(无 imageToImage mode 也出现)',
(tester) async {
// _fakeVideoCaps.modes = [textToVideo, imageToVideo]——无 imageToImage,
// 若共享 section 误用 image 门此测必红。
await _pumpVideoInspector(tester, capsList: [_fakeVideoCaps]);
expect(find.byType(CharactersSection), findsOneWidget);
});

testWidgets('maxRefImages=0 → 角色区整段不挂载', (tester) async {
await _pumpVideoInspector(tester, capsList: [_noCameraCaps]);
expect(find.byType(CharactersSection), findsNothing);
});

testWidgets('点选角色 chip → saveConfig 写 character_ids', (tester) async {
// 角色仓储种一条 Character(id: 'ch-1'),tap chip 后断言
// fake canvas 仓储收到的 typeConfig 含 {'character_ids': ['ch-1']}
// (断言写法对照 image_config_inspector_characters_test.dart 既有 toggle 用例)。
});
}
```

(第三例的完整断言按 image 侧同名场景照抄改 caps;若 image 侧无现成 toggle 落库断言则经 fake_repositories 的 canvas 仓储 update 记录断。)

- [ ] **Step 2: 跑测试确认红**

```bash
flutter test test/features/canvas/widgets/video_config_inspector_characters_test.dart 2>&1 | tail -3
```

Expected: FAIL(CharactersSection findsNothing——video inspector 尚未挂载)。

- [ ] **Step 3: 实现挂载(video_config_inspector.dart)**

import 增 `'characters_section.dart'`;`build` 内 NodeInputsSection 块之前插入:

```dart
// CH-2:角色区(CH-1 视频注入的用户入口)。门控对齐注入门:
// 仅 maxRefImages>0 挂载——与 image 侧「常挂+警示文案」有意不同,
// 视频多数 provider 无 ref 能力,常挂=一屏死区。
if (selected != null && selected.maxRefImages > 0) ...[
const SizedBox(height: InkSpacing.lg),
CharactersSection(
targetNode: widget.node,
selectedCaps: selected,
requireImageToImageMode: false,
),
],
```

- [ ] **Step 4: 测试绿 + 双 inspector 回归**

```bash
flutter test test/features/canvas/widgets/ 2>&1 | tail -2
flutter analyze lib test 2>&1 | tail -1
```

Expected: 全过、no issues。

- [ ] **Step 5: 提交**

```bash
git add lib/features/canvas/widgets test/features/canvas/widgets
git commit -m "feat(canvas): CH-2 视频 Inspector 角色区——CH-1 注入自此有用户入口,门控 maxRefImages>0 对齐注入口径

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"
```

### Task 3: 状态回填 + 全量闸门 + PR

**Files:**
- Modify: `docs/BOARD.md`(近期落地表 #228 行后加行)
- Modify: `docs/MASTERPLAN.md:88`(E5 行 CH-2 标 ✅)
- Modify: `docs/superpowers/plans/2026-07-07-launch-features.md:146`(CH-2 卡加状态行)

- [ ] **Step 1: 三处回填**(PR 号预写 #229,建 PR 时核实)

MASTERPLAN E5 行:`video inspector 角色区` → `video inspector 角色区(**CH-2 ✅ #229**)`,并把括号注里「用户可见入口随 CH-2」句改为「用户可见入口已随 CH-2 落地」。

features plan CH-2 卡后加:

```markdown
状态:已随 #229 落地——三类抽共享 characters_section.dart(NameDialog/CharacterChip
一并转公开,_PresetsSection 改 import;image inspector 减重 ~340 行零行为变化,
既有 10 例角色区测试一行未改全绿);video 侧 maxRefImages>0 门控挂载,
requireImageToImageMode=false 对齐 CH-1 注入口径。
```

BOARD 近期落地表:

```markdown
| CH-2 视频 Inspector 角色区:角色区/命名框/角色 chip 抽共享 `characters_section.dart`(image inspector 1033→约 690 行,行为零变化以既有 10 例测试一行未改全绿为证)+ video inspector 门控挂载(`maxRefImages>0` 对齐 CH-1 注入门,不检查 imageToImage——r2v/omni 语义;与 image 侧常挂+警示有意不同)——CH-1 视频角色注入自此有用户可见入口 | #229 |
```

- [ ] **Step 2: 全量闸门 + 提交 + push(pbcopy 交用户 `!` 执行)+ PR + CI 绿合并**

```bash
flutter analyze lib test && flutter test --exclude-tags golden
git add -A docs && git commit -m "docs: CH-2 收口回填——BOARD/MASTERPLAN/features 计划三处状态

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>"
printf 'git push -u origin feat/ch-2-video-inspector-characters' | pbcopy # 用户 ! 执行
gh pr create --title "feat: CH-2 视频 Inspector 角色区——抽共享 section + CH-1 注入的用户入口" --body "(正文技术记号一律反引号,禁裸 @)"
gh pr checks <PR#> --watch && gh pr merge <PR#> --squash --delete-branch
```
Loading
Loading