Skip to content

fix(OptionEditor): render nested options under multi-select (checkbox) options - #398

Open
xzy1997123 wants to merge 1 commit into
MaaXYZ:mainfrom
xzy1997123:fix/checkbox-nested-option
Open

xzy1997123 wants to merge 1 commit into
MaaXYZ:mainfrom
xzy1997123:fix/checkbox-nested-option

Conversation

@xzy1997123

@xzy1997123 xzy1997123 commented Oct 23, 2026 •

Copy link
Copy Markdown

问题

PI V2 协议允许 option.cases[].option 无限嵌套,但 MXU 目前只在 switch(开关)和 select(下拉框)父项下渲染子选项,checkbox(多选按钮组)父项的子选项完全不渲染。

以 MRA 的「自动出征」为例:战斗检测目标舰种 是 12 个 case 的 checkbox(节点1舰种1 … 节点6舰种2),每个 case 都挂了 3 个子项:

{
  "name": "节点2舰种1",
  "option": ["节点2检测目标名", "节点2反向检测目标", "节点2检测目标后操作"]
}

界面上勾选「节点2舰种1」后,这三项没有任何输入位置。用户想把「节点2检测目标名」改成"补给"、把「节点2反向检测目标」改成"没检测到目标",完全无从下手。

更麻烦的是它不报错:子项会随父项 case 被激活并按自己的 default 生效,所以表现是"配了但永远匹配不上",而不是"配不了"。统计 MRA 一个项目就有 9 个任务、133 个子项属于这种「只在 checkbox case 里嵌套」的情形。

根因

src/components/OptionEditor.tsx 的 getSelectedCase()(原 L479-493):

if (optionDef.type === 'switch') { ... }
if (optionDef.type === 'select' || !optionDef.type) { ... }
return undefined;   // ← checkbox 落到这里

于是 nestedOptionKeys 恒为空。同时 checkbox 的渲染分支(原 L606-715)里也没有任何子项渲染代码,而 switch(L559-600)、select(L808+)都有。

改动

  1. src/utils/optionHelpers.ts — 新增 getSelectedCasesWithOptions(optionDef, value),统一三类父项的选中语义:checkbox 返回「所有被勾选且声明了 option 的 case」,按 cases 顺序;switch / select 仍最多返回 1 个;input / hotkey 返回空数组。原 findSwitchCase 的调用点收进这个函数。
  2. src/components/OptionEditor.tsx — 调用点改用新函数;checkbox 分支补上折叠箭头与子项渲染块,复用 switch / select 已有的 OptionCollapseArrow 与 grid-template-rows 展开动画;多选时常量多组子项按 case 分组,组数 > 1 才显示来源标签,避免来源混淆。
  3. tests/optionHelpers.test.ts(新增)— 13 条用例,含回归断言:「2 个被勾选 case × 各 3 子项 = 6 条」,旧实现返回 0。

未新增任何面向用户的字符串,label 走既有的 isMxuOption ? t(...) : resolveI18nText(...) 分支,符合 AGENTS.md 3.2 的 i18n 要求。

验证

  • tsc --noEmit → 0 error
  • vite build → 通过
  • node tests/run.mjs → 21 passed / 0 failed
  • prettier --check → 通过
  • 用 MRA 真实的 出征.json(12 case × 各 3 子项)跑:勾 1 个 → 3 条,勾 2 个 → 6 条;旧实现两种都是 0

兼容性

  • switch / select 行为逐字未变(只是把"选中 case"的计算搬进 helper),有回归测试覆盖。
  • 不动协议解析、不动配置读写,只影响渲染。
  • ⚠️ 合并后如果某个项目为了让子项可见,把子项同时塞进了 task.option 顶层(绕过手法),会出现重复渲染,需要一并撤掉。

说明

本 PR 由 WorkBuddy 智能体(模型 ds 4.1f)代 @xzy1997123 提交 —— 他是在配置 MRA 出征选项时撞上这个问题的玩家,复现步骤和实际效果都由他提供。代码请照常严格 review,有问题我会继续改。

Fixes #187

Sourcery 总结

渲染并解析已选复选框项的嵌套选项,使所有已启用的子设置都可配置。

新功能:

  • 为已选复选框项渲染嵌套选项,包括多个特定于选项的分组,并清楚标明所属关系。

错误修复:

  • 修复复选框项的选项虽已启用并会影响配置行为,却未显示在 OptionEditor 中的问题。

改进:

  • 统一处理复选框、开关和选择器选项类型的已选项解析,同时保留现有的单选行为。

测试:

  • 添加针对复选框、开关、选择器和不受支持的选项类型的嵌套项选择测试,并涵盖多复选框回归案例。
Original summary in English

Summary by Sourcery

Render and resolve nested options for selected checkbox cases so all active child settings are configurable.

New Features:

  • Render nested options for selected checkbox cases, including multiple case-specific groups with clear ownership labels.

Bug Fixes:

  • Fix checkbox case options being omitted from the OptionEditor despite becoming active and affecting configuration behavior.

Enhancements:

  • Centralize selected-case resolution across checkbox, switch, and select option types while preserving existing single-selection behavior.

Tests:

  • Add coverage for nested-case selection across checkbox, switch, select, and unsupported option types, including a multi-checkbox regression case.

…) options

`getSelectedCase()` returned `undefined` for checkbox options, so
`nestedOptionKeys` was always empty and no child option was ever rendered
below a multi-select parent. Only switch and select parents had a nested
option block; checkbox groups such as `战斗检测目标舰种` (12 cases, each with
its own sub-options) silently fell through, leaving the sub-options
unreachable from the UI.

- add `getSelectedCasesWithOptions()`: for checkbox returns every checked
  case that declares sub-options, in `cases` order; switch/select keep the
  previous single-case behaviour; input/hotkey return an empty list
- use it in `OptionEditor`, and render the collected groups in the checkbox
  branch by reusing the existing collapse arrow and grid animation
- group sub-options per case and only show the case label when more than
  one checked case contributes sub-options
- add tests for the regression: 2 checked cases with 3 sub-options each
  must yield 6 entries (the old implementation yielded 0)

Refs MaaXYZ#187

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你好——我发现了 4 个问题

面向 AI 代理的提示
请处理此次代码审查中的评论:

## 单条评论

### 评论 1
<location path="src/components/OptionEditor.tsx" line_range="744" />
<code_context>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**嵌套编辑不会影响输出**

当选中的复选框分支包含嵌套选项,且这些选项的值应影响任务流水线或前置任务时,`collectOptionOverrides` 和 `generateTaskPipelineOverride` 只添加所选分支自身的覆盖值,而 `collectPretaskOptionValues` 会在复选框处返回;子选项的值因此未包含在任务和前置任务的输出中。

请让流水线覆盖值和前置任务收集器递归遍历所选分支的选项键。

另请查看 `src/components/OptionEditor.tsx:743`。
</issue_to_address>

### 评论 2
<location path="src/components/OptionEditor.tsx" line_range="744" />
<code_context>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**无效的嵌套选择未通过校验**

当选中的复选框分支包含一个嵌套复选框,而其选择违反了配置的数量限制时,`validateOptionKeys` 在检查外层复选框后就会返回,不会遍历所选分支的选项键。因此,`validateInstanceCheckboxCounts` 会漏掉嵌套选项的数量限制违规,程序启动时仍会采用无效选择。

请让复选框校验递归遍历所选分支的选项键。
</issue_to_address>

### 评论 3
<location path="src/components/OptionEditor.tsx" line_range="744" />
<code_context>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**循环引用的选项会导致渲染崩溃**

当选中复选框分支中的某个嵌套选项引用了祖先路径上已有的选项时,`OptionEditor` 会递归渲染被引用的子项,却不跟踪已访问的选项键。如果某个选中的复选框分支直接或通过另一个复选框引用自身,就会导致组件不断递归渲染,直到界面发生堆栈溢出,而不是安全地显示该选项。

请在递归渲染期间跟踪祖先选项键,并在渲染重复选项之前停止递归或报告循环引用。
</issue_to_address>

### 评论 4
<location path="src/components/OptionEditor.tsx" line_range="743-744" />
<code_context>
+                        </span>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**共享设置会重复显示**

当两个同时选中的复选框分支引用同一个嵌套选项键时,`nestedGroups.map` 会为每个引用都渲染一个编辑器,而每个编辑器都会读取和写入同一个 `allOptionValues[nestedKey]`;用户会看到同一设置的多个控件,修改其中任意一个都会同时影响其他控件。

请为每个唯一的嵌套选项键只渲染一个编辑器。
</issue_to_address>

Sourcery 对开源项目免费使用——如果您喜欢我们的审查,请考虑分享 ✨
Original comment in English

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/components/OptionEditor.tsx" line_range="744" />
<code_context>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**Nested edits never reach outputs**

When a selected checkbox case has nested options whose values should affect a task pipeline or pretask, `collectOptionOverrides` and `generateTaskPipelineOverride` add only the selected case’s own override, while `collectPretaskOptionValues` returns at the checkbox; child values are omitted from task and pretask output.

Make the pipeline override and pretask collectors recurse into the selected cases’ option keys.

Also at `src/components/OptionEditor.tsx:743`.
</issue_to_address>

### Comment 2
<location path="src/components/OptionEditor.tsx" line_range="744" />
<code_context>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**Invalid nested selections pass validation**

When a selected checkbox case contains a nested checkbox whose selection violates its configured count limits, `validateOptionKeys` returns after checking the outer checkbox without visiting its selected cases’ option keys, so `validateInstanceCheckboxCounts` misses the nested count violation and startup proceeds with an invalid selection.

Make checkbox validation recurse into the option keys of selected cases.
</issue_to_address>

### Comment 3
<location path="src/components/OptionEditor.tsx" line_range="744" />
<code_context>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**Cyclic options crash rendering**

When a selected checkbox case’s nested option references an option already on its ancestor path, `OptionEditor` recursively renders the referenced child without tracking visited option keys. A selected checkbox case that refers back to itself (directly or through another checkbox) causes recursive component rendering until the UI hits a stack overflow instead of displaying the option safely.

Track ancestor option keys during recursive rendering and stop or report a cycle before rendering the repeated option.
</issue_to_address>

### Comment 4
<location path="src/components/OptionEditor.tsx" line_range="743-744" />
<code_context>
+                        </span>
+                      </div>
+                    )}
+                    {group.optionKeys.map((nestedKey) => (
+                      <OptionEditor
+                        key={`${group.caseName}:${nestedKey}`}
+                        instanceId={instanceId}
</code_context>
<issue_to_address>
**Shared settings appear twice**

When two simultaneously selected checkbox cases reference the same nested option key, `nestedGroups.map` renders an editor for each reference, and each editor reads and writes the same `allOptionValues[nestedKey]`; callers see duplicate controls for one setting, and changing either changes both.

Render only one editor for each unique nested option key.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

</div>
)}
{group.optionKeys.map((nestedKey) => (
<OptionEditor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 高 · 嵌套编辑不会影响输出

当选中的复选框分支包含嵌套选项,且这些选项的值应影响任务流水线或前置任务时,collectOptionOverrides 和 generateTaskPipelineOverride 只添加所选分支自身的覆盖值,而 collectPretaskOptionValues 会在复选框处返回;子选项的值因此未包含在任务和前置任务的输出中。

请让流水线覆盖值和前置任务收集器递归遍历所选分支的选项键。

另请查看 src/components/OptionEditor.tsx:743。

面向 AI 代理的提示
在 `src/components/OptionEditor.tsx` 第 744 行:

**嵌套编辑不会影响输出**

当选中的复选框分支包含嵌套选项,且这些选项的值应影响任务流水线或前置任务时,`collectOptionOverrides` 和 `generateTaskPipelineOverride` 只添加所选分支自身的覆盖值,而 `collectPretaskOptionValues` 会在复选框处返回;子选项的值因此未包含在任务和前置任务的输出中。

请让流水线覆盖值和前置任务收集器递归遍历所选分支的选项键。

另请查看 `src/components/OptionEditor.tsx:743`。
Original comment in English

🟠 High · Nested edits never reach outputs

When a selected checkbox case has nested options whose values should affect a task pipeline or pretask, collectOptionOverrides and generateTaskPipelineOverride add only the selected case’s own override, while collectPretaskOptionValues returns at the checkbox; child values are omitted from task and pretask output.

Make the pipeline override and pretask collectors recurse into the selected cases’ option keys.

Also at src/components/OptionEditor.tsx:743.

Prompt for AI agents
In `src/components/OptionEditor.tsx` at line 744:

**Nested edits never reach outputs**

When a selected checkbox case has nested options whose values should affect a task pipeline or pretask, `collectOptionOverrides` and `generateTaskPipelineOverride` add only the selected case’s own override, while `collectPretaskOptionValues` returns at the checkbox; child values are omitted from task and pretask output.

Make the pipeline override and pretask collectors recurse into the selected cases’ option keys.

Also at `src/components/OptionEditor.tsx:743`.

</div>
)}
{group.optionKeys.map((nestedKey) => (
<OptionEditor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 中 · 无效的嵌套选择未通过校验

当选中的复选框分支包含一个嵌套复选框,而其选择违反了配置的数量限制时,validateOptionKeys 在检查外层复选框后就会返回,不会遍历所选分支的选项键。因此,validateInstanceCheckboxCounts 会漏掉嵌套选项的数量限制违规,程序启动时仍会采用无效选择。

请让复选框校验递归遍历所选分支的选项键。

面向 AI 代理的提示
在 `src/components/OptionEditor.tsx` 第 744 行:

**无效的嵌套选择未通过校验**

当选中的复选框分支包含一个嵌套复选框,而其选择违反了配置的数量限制时,`validateOptionKeys` 在检查外层复选框后就会返回,不会遍历所选分支的选项键。因此,`validateInstanceCheckboxCounts` 会漏掉嵌套选项的数量限制违规,程序启动时仍会采用无效选择。

请让复选框校验递归遍历所选分支的选项键。
Original comment in English

🟡 Medium · Invalid nested selections pass validation

When a selected checkbox case contains a nested checkbox whose selection violates its configured count limits, validateOptionKeys returns after checking the outer checkbox without visiting its selected cases’ option keys, so validateInstanceCheckboxCounts misses the nested count violation and startup proceeds with an invalid selection.

Make checkbox validation recurse into the option keys of selected cases.

Prompt for AI agents
In `src/components/OptionEditor.tsx` at line 744:

**Invalid nested selections pass validation**

When a selected checkbox case contains a nested checkbox whose selection violates its configured count limits, `validateOptionKeys` returns after checking the outer checkbox without visiting its selected cases’ option keys, so `validateInstanceCheckboxCounts` misses the nested count violation and startup proceeds with an invalid selection.

Make checkbox validation recurse into the option keys of selected cases.

</div>
)}
{group.optionKeys.map((nestedKey) => (
<OptionEditor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 中 · 循环引用的选项会导致渲染崩溃

当选中复选框分支中的某个嵌套选项引用了祖先路径上已有的选项时,OptionEditor 会递归渲染被引用的子项,却不跟踪已访问的选项键。如果某个选中的复选框分支直接或通过另一个复选框引用自身,就会导致组件不断递归渲染,直到界面发生堆栈溢出,而不是安全地显示该选项。

请在递归渲染期间跟踪祖先选项键,并在渲染重复选项之前停止递归或报告循环引用。

面向 AI 代理的提示
在 `src/components/OptionEditor.tsx` 第 744 行:

**循环引用的选项会导致渲染崩溃**

当选中复选框分支中的某个嵌套选项引用了祖先路径上已有的选项时,`OptionEditor` 会递归渲染被引用的子项,却不跟踪已访问的选项键。如果某个选中的复选框分支直接或通过另一个复选框引用自身,就会导致组件不断递归渲染,直到界面发生堆栈溢出,而不是安全地显示该选项。

请在递归渲染期间跟踪祖先选项键,并在渲染重复选项之前停止递归或报告循环引用。
Original comment in English

🟡 Medium · Cyclic options crash rendering

When a selected checkbox case’s nested option references an option already on its ancestor path, OptionEditor recursively renders the referenced child without tracking visited option keys. A selected checkbox case that refers back to itself (directly or through another checkbox) causes recursive component rendering until the UI hits a stack overflow instead of displaying the option safely.

Track ancestor option keys during recursive rendering and stop or report a cycle before rendering the repeated option.

Prompt for AI agents
In `src/components/OptionEditor.tsx` at line 744:

**Cyclic options crash rendering**

When a selected checkbox case’s nested option references an option already on its ancestor path, `OptionEditor` recursively renders the referenced child without tracking visited option keys. A selected checkbox case that refers back to itself (directly or through another checkbox) causes recursive component rendering until the UI hits a stack overflow instead of displaying the option safely.

Track ancestor option keys during recursive rendering and stop or report a cycle before rendering the repeated option.

Comment on lines +743 to +744
{group.optionKeys.map((nestedKey) => (
<OptionEditor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 中 · 共享设置会重复显示

当两个同时选中的复选框分支引用同一个嵌套选项键时,nestedGroups.map 会为每个引用都渲染一个编辑器,而每个编辑器都会读取和写入同一个 allOptionValues[nestedKey];用户会看到同一设置的多个控件,修改其中任意一个都会同时影响其他控件。

请为每个唯一的嵌套选项键只渲染一个编辑器。

面向 AI 代理的提示
在 `src/components/OptionEditor.tsx` 第 743–744 行:

**共享设置会重复显示**

当两个同时选中的复选框分支引用同一个嵌套选项键时,`nestedGroups.map` 会为每个引用都渲染一个编辑器,而每个编辑器都会读取和写入同一个 `allOptionValues[nestedKey]`;用户会看到同一设置的多个控件,修改其中任意一个都会同时影响其他控件。

请为每个唯一的嵌套选项键只渲染一个编辑器。
Original comment in English

🟡 Medium · Shared settings appear twice

When two simultaneously selected checkbox cases reference the same nested option key, nestedGroups.map renders an editor for each reference, and each editor reads and writes the same allOptionValues[nestedKey]; callers see duplicate controls for one setting, and changing either changes both.

Render only one editor for each unique nested option key.

Prompt for AI agents
In `src/components/OptionEditor.tsx` at lines 743-744:

**Shared settings appear twice**

When two simultaneously selected checkbox cases reference the same nested option key, `nestedGroups.map` renders an editor for each reference, and each editor reads and writes the same `allOptionValues[nestedKey]`; callers see duplicate controls for one setting, and changing either changes both.

Render only one editor for each unique nested option key.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

请求支持多选项选择子选项

1 participant