Skip to content

feat(front): 谱面管理左侧列表搜索、1440 断点修复、右侧编辑区空态 - #80

Merged
clansty merged 14 commits into
MuNET-OSS:mainfrom
Misaka-0x447f:feat/misaka/20261101
Nov 1, 2026
Merged

clansty merged 14 commits into
MuNET-OSS:mainfrom
Misaka-0x447f:feat/misaka/20261101

Conversation

@Misaka-0x447f

@Misaka-0x447f Misaka-0x447f commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

改动

1. 谱面管理左侧列表加搜索框 —— 按名称 / 作曲 / 谱师 / ID 过滤(补零的 6 位 ID 也能搜),复用 utils/musicSearch 的 matchMusicKeyword,与批量操作页同口径;过滤只作用于渲染列表,选中曲被过滤掉时右侧编辑面板不会变空。

搜索框

2. 修 1440 宽断点重叠 —— 抽屉布局的 max-[1440px] 与汉堡按钮的 min-[1440px]:hidden 在正好 1440px 时同时命中:左侧被推出屏幕、打开它的按钮又被隐藏,整页不可用;抽屉改为 max-[1439px]。上面这张和下面两张都是在 1440 宽下采集的,左侧列表固定展开即为修复后的效果。

3. A000 目录下编辑按钮保留但禁用 —— 按钮不再消失(避免位置跳动);并且是真正禁用:按钮 disabled、保存的 Ctrl+S 快捷键与导入入口都在入口处拦截,不再只靠样式挡住鼠标;提示改为灰色小字放在按钮下面。

A000 禁用态

4. 未选中谱面时右侧给空态提示 —— 编辑区不再整块空白,居中「请选择谱面以编辑」,24px、0.5 不透明度,三语同步。

空态提示

验证

真实后端(1671 首)浏览器实测:Glorious→2、Mournfinale→4、1005→2、zzzz→0、夜→11;深位置跳转 scrollTop 0→83833 定位成功;1440 宽下 matchMedia('(max-width:1439px)') 为 false;pnpm typecheck 与 pnpm build 通过。两轮 code review。

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)

Sourcery 摘要

改进谱面管理搜索和空状态,修复 1440px 布局边界问题,并确保 A000 目录为只读。

新功能:

  • 支持按标题、作曲者、谱师,以及原始和补零后的 ID 搜索筛选曲目列表,并提供本地化的无结果和清除搜索状态。
  • 编辑器未选中谱面时显示本地化的空状态。

错误修复:

  • 修复 1440px 响应式断点问题,确保在恰好 1440px 时谱面列表抽屉和菜单控件仍可正常使用。
  • 确保 A000 目录在编辑、保存、删除、导入、新建、替换和更新封面等操作中始终为只读,同时保留禁用的控件和说明信息。
  • 在筛选、切换面板或打开响应式抽屉时保留已选谱面,并确保列表导航可靠。
Original summary in English

Sourcery 总结

通过添加可搜索的导航、可靠的响应式行为、清晰的编辑器空状态,以及强制对 A000 目录进行只读处理,改进图表管理。

新功能:

  • 为图表列表添加本地化搜索和无匹配结果提示,并支持匹配原始歌曲 ID 和补零后的歌曲 ID。
  • 未选择图表进行编辑时,显示本地化提示。

错误修复:

  • 修复图表管理布局在宽度恰好为 1440px 时的响应式行为。
  • 在筛选、切换面板或打开响应式抽屉时,保留图表导航状态和选中项。

改进:

  • 将 A000 目录统一设为只读,同时保留已禁用的编辑控件和说明信息。
  • 在异步导入、替换、封面上传、保存、删除和创建流程中,针对目录变更和只读目标添加保护。
Original summary in English

Sourcery 总结

通过支持搜索的导航、可靠的响应式行为、清晰的编辑器空状态,以及对 A000 目录统一的只读处理,改进图表管理。

新功能:

  • 添加本地化的图表列表搜索,支持按标题、作曲者、编曲者以及原始或补零后的 ID 搜索,并提供清除和无结果状态。
  • 未选择图表进行编辑时,显示本地化的空状态。

错误修复:

  • 修复在恰好 1440px 时图表管理布局的响应式行为。
  • 在筛选、切换面板或打开响应式抽屉时保留图表选择,并确保导航可靠。

改进:

  • 将 A000 目录设为只读,涵盖图表编辑、保存、删除、导入、创建、替换和封面更新,同时保留禁用的控件和说明文字。
  • 通过将异步写入操作绑定到其发起时所在的资源目录,防止目录变更影响这些操作。
Original summary in English

Sourcery 摘要

通过支持搜索的导航、可靠的响应式布局、清晰的编辑器空状态,以及对 A000 目录强制执行只读处理,改进图表管理。

新功能:

  • 为图表列表添加本地化搜索,支持按标题、作曲者、制谱者以及原始或补零后的歌曲 ID 搜索,并提供清除搜索和无搜索结果状态。
  • 未选择图表进行编辑时,显示本地化的空状态。

错误修复:

  • 修复图表管理布局在 1440px 时的响应式行为。
  • 在筛选、切换面板或打开响应式抽屉时,保留图表选择并确保导航可靠。

改进:

  • 统一将 A000 目录设为只读,同时保留禁用的控件和说明文字,并对保存、删除、导入、创建、替换和封面更新操作进行保护。
  • 确保异步图表操作始终绑定到其发起时所在的资源目录,避免目录变更导致写入被重定向。
Original summary in English

Summary by Sourcery

Improve chart management with searchable navigation, reliable responsive behavior, a clear editor empty state, and enforced read-only handling for the A000 directory.

New Features:

  • Add localized chart-list search across title, composer, charter, and raw or zero-padded song IDs, with clear and no-results states.
  • Show a localized empty state when no chart is selected for editing.

Bug Fixes:

  • Fix responsive chart-management layout behavior at exactly 1440px.
  • Preserve chart selection and make navigation reliable when filtering, switching panels, or opening the responsive drawer.

Enhancements:

  • Make the A000 directory consistently read-only while retaining disabled controls and explanatory text, including protection for save, delete, import, create, replace, and jacket updates.
  • Keep asynchronous chart operations bound to their originating asset directory to prevent directory changes from redirecting writes.

- 按名称 / 作曲 / 谱师 / ID 过滤,命中口径复用 utils/musicSearch 的 matchMusicKeyword,
  和批量操作页保持同一口径
- 过滤只作用于渲染列表(displayedMusic),selectedMusic 仍由未过滤的 musicList 派生,
  所以选中曲被过滤掉时右侧编辑面板不会变空
- 无匹配显示空态;有内容时显示清空按钮,Esc 也能清空;输入法组字中按 Esc 不清空
- 修既有跳转失效(只选中不滚动):批量页调用方只等 100ms,而 tab 切换是 0.3s 的 out-in
  过渡,那时 VList 还没挂载。改成等 ref 就绪(setTimeout 轮询,2s 兜底)
- 修既有跳转失效(左侧停在目录管理时):跳转先把 leftPanel 切回列表再定位
- 搜索框占位符去掉「别名」:matchMusicKeyword 本来就不搜别名,且这个 key 被批量操作页
  与工具页共用,三处文案随实现一起对齐

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 抽屉布局原来写 max-[1440px],而汉堡按钮是 min-[1440px]:hidden,两边在正好 1440 宽时
  同时命中:左侧被 translate 推出屏幕、打开它的按钮又被隐藏,谱面管理页完全没法用。
  1440 宽的屏幕把窗口最大化就会踩到
- 抽屉改成 max-[1439px],与 min-[1440px] 以 1440 为界且不重叠;1440 及以上固定展开
- 1439 及以下行为不变(原来的 max-width:1440 同样覆盖 1439),只有正好 1440 从坏掉变成桌面布局

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 原来选中 A000(游戏本体目录)时,右上角直接换成一句提示文案,按钮消失,位置跟着跳
- 改成按钮始终在,用 pointer-events-none + op-50 禁用;提示改为灰色小字放在按钮下面
- 复制与导出不在此列,它不改 A000 的内容,保持可用

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 没选中任何曲目时右侧编辑区原本整块空白,看不出是「没选」还是「没加载出来」
- 补一个居中的「请选择谱面以编辑」,0.5 不透明度、稍大字号(三语同步)

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
@sourcery-ai

sourcery-ai Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

审查者指南

此 PR 为图表列表添加了共享逻辑的音乐搜索,增强了跳转到虚拟化列表的可靠性,修复了精确的 1440px 响应式断点,并在所有受支持的语言环境中明确了编辑器的只读状态和未选择状态。

筛选后的图表导航时序图

sequenceDiagram
    participant BatchPage
    participant MusicList
    participant VList
    participant MusicStore

    BatchPage->>MusicList: scrollToMusic(musicId)
    MusicList->>MusicStore: Check musicList and selectedADir
    MusicList->>MusicList: clearMusicSearch()
    MusicList->>MusicList: nextTick()
    loop Until VList mounts or timeout
        MusicList->>VList: waitForMusicList()
    end
    MusicList->>VList: scrollToIndex(index, align, smooth)
    VList-->>BatchPage: Target chart visible
Loading

图表列表搜索与编辑器状态流程图

flowchart LR
    Input["搜索关键词"] --> Match[matchMusicKeyword]
    MusicList["musicList"] --> Match
    Match --> Displayed["displayedMusic"]
    Displayed --> VList["筛选后的虚拟列表"]
    MusicList --> Selected["selectedMusic"]
    Selected --> Editor["编辑器面板"]
    Selected -->|无| Empty["选择一个图表进行编辑"]
    Displayed -->|无匹配项| NoMatch["没有匹配的歌曲"]
Loading

文件级变更

变更 详情 文件
添加可搜索的音乐列表筛选,同时保留已选图表并确保跨页面跳转可靠。
  • 添加关键词输入框、按 Escape 键或通过控件清除搜索、无匹配项状态以及本地化标签。
  • 使用共享的音乐关键词匹配器筛选渲染的行,同时根据完整列表确定所选项。
  • 跳转导航时切回音乐列表、清除会隐藏目标的搜索词、等待虚拟列表挂载,并取消过期的跳转。
MaiChartManager/Front/src/views/Charts/MusicList/index.tsx
MaiChartManager/Front/src/locales/en.yaml
MaiChartManager/Front/src/locales/zh-TW.yaml
MaiChartManager/Front/src/locales/zh.yaml
修复 1440px 响应式布局边界,并改进图表编辑面板状态。
  • 将抽屉样式的最大宽度改为 1439px,避免与 min-width 1440px 的汉堡按钮规则重叠。
  • 保留 A000 编辑控件的显示,但将其禁用;同时显示本地化说明,并保留复制/导出操作。
  • 未选择图表时,显示本地化且居中的空状态。
MaiChartManager/Front/src/views/Charts/index.tsx
MaiChartManager/Front/src/locales/en.yaml
MaiChartManager/Front/src/locales/zh-TW.yaml
MaiChartManager/Front/src/locales/zh.yaml

提示与命令

使用 Sourcery

  • 触发新的审查: 在拉取请求中评论 @sourcery-ai review。
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 根据审查评论创建 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。也可以在审查评论下回复 @sourcery-ai issue 来创建 issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写入 @sourcery-ai,即可随时生成标题。也可以在拉取请求中评论 @sourcery-ai title,随时(重新)生成标题。
  • 生成拉取请求摘要: 在拉取请求正文中想要插入摘要的位置写入 @sourcery-ai summary,即可随时在该位置生成 PR 摘要。也可以在拉取请求中评论 @sourcery-ai summary,随时(重新)生成摘要。
  • 生成审查者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时(重新)生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已处理完所有评论且不想再看到它们,此功能会很有用。
  • 忽略所有 Sourcery 审查: 在拉取请求中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。如果你想重新开始新一轮审查,这尤其有用——别忘了评论 @sourcery-ai review 来触发新的审查!

自定义使用体验

访问你的控制面板,以便:

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

The PR adds shared-logic music search to the chart list, hardens navigation into the virtualized list, fixes the exact 1440px responsive breakpoint, and clarifies read-only and no-selection states in the editor across all supported locales.

Sequence diagram for filtered chart navigation

sequenceDiagram
    participant BatchPage
    participant MusicList
    participant VList
    participant MusicStore

    BatchPage->>MusicList: scrollToMusic(musicId)
    MusicList->>MusicStore: Check musicList and selectedADir
    MusicList->>MusicList: clearMusicSearch()
    MusicList->>MusicList: nextTick()
    loop Until VList mounts or timeout
        MusicList->>VList: waitForMusicList()
    end
    MusicList->>VList: scrollToIndex(index, align, smooth)
    VList-->>BatchPage: Target chart visible
Loading

Flow diagram for chart list search and editor state

flowchart LR
    Input["Search keyword"] --> Match[matchMusicKeyword]
    MusicList["musicList"] --> Match
    Match --> Displayed["displayedMusic"]
    Displayed --> VList["Filtered virtual list"]
    MusicList --> Selected["selectedMusic"]
    Selected --> Editor["Editor panel"]
    Selected -->|none| Empty["Select a chart to edit"]
    Displayed -->|no matches| NoMatch["No matching songs"]
Loading

File-Level Changes

Change Details Files
Add searchable music-list filtering while preserving the selected chart and making cross-page jumps reliable.
  • Add keyword input, clear-on-Escape/control, no-match state, and localized labels.
  • Filter rendered rows with the shared music keyword matcher while deriving selection from the full list.
  • Make jump navigation switch back to the music list, clear a hiding search term, wait for the virtual list to mount, and cancel stale jumps.
MaiChartManager/Front/src/views/Charts/MusicList/index.tsx
MaiChartManager/Front/src/locales/en.yaml
MaiChartManager/Front/src/locales/zh-TW.yaml
MaiChartManager/Front/src/locales/zh.yaml
Resolve the 1440px responsive-layout boundary and improve the chart edit panel states.
  • Change drawer styles to max-width 1439px so they do not overlap the min-width 1440px hamburger-button rule.
  • Keep A000 editing controls visible but disabled, with an explanatory localized note while retaining copy/export actions.
  • Render a localized centered empty state when no chart is selected.
MaiChartManager/Front/src/views/Charts/index.tsx
MaiChartManager/Front/src/locales/en.yaml
MaiChartManager/Front/src/locales/zh-TW.yaml
MaiChartManager/Front/src/locales/zh.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@cubic-dev-ai cubic-dev-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.

All reported issues were addressed across 5 files

Reply to a comment to ask cubic a question or push back. It learns from your replies.

View guided diff | Re-trigger cubic

@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 个问题

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

## Individual Comments

### Comment 1
<location path="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="76" />
<code_context>
+                <div class="flex flex-wrap items-center justify-end gap-2 max-[639px]:w-full">
+                  {!!selectedMusic.value && <div class="shrink-0"><CopyToButton /></div>}
+                  {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+                  <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
                     <div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
                     <div class="shrink-0"><ImportCreateChartButton /></div>
</code_context>
<issue_to_address>
**A000 remains editable by keyboard**

When A000 is selected and a user activates a wrapped control with the keyboard or presses Ctrl+S, the `pointer-events-none` wrapper does not disable its child buttons or guard their handlers, so `DeleteMusic` and import/create actions can still modify the read-only directory; the mounted save shortcut can also run through Ctrl+S.

Disable or guard the wrapped actions and save shortcut whenever A000 is selected.
</issue_to_address>

### Comment 2
<location path="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="78" />
<code_context>
+                  {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+                  <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
                     <div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
                     <div class="shrink-0"><ImportCreateChartButton /></div>
-                  </>
-                )}
</code_context>
<issue_to_address>
**Open create dialog targets A000**

When a create dialog is open in an editable directory and another tab changes the shared selected directory to A000 before confirmation, `ImportCreateChartButton` stays mounted when the selected directory changes, so its already-open create dialog remains active. `CreateMusicModal.save` reads the live `selectedADir` rather than the directory where the dialog was opened; confirming after another tab changes the shared directory selection to A000 calls `AddMusic` for A000, which the controller accepts.

Capture the directory when opening the dialog and reject or close the dialog if that directory is A000; also enforce the read-only rule in the mutation endpoint.
</issue_to_address>

### Comment 3
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="40" />
<code_context>
+  if (!musicList.value.some(m => m.id === musicId)) return;
+
+  // 跳转的含义是「回到曲目列表并看到这首歌」:左侧停在目录管理时也要切回列表
+  if (leftPanel.value !== 'musicList') leftPanel.value = 'musicList';
+
+  // 搜索词把目标挡住了就先清掉,否则 scrollToIndex 找不到它
</code_context>
<issue_to_address>
**Jump target stays hidden**

When a batch-page jump runs below 1440px, `scrollToMusic` switches `leftPanel` to `musicList` but does not open the responsive drawer, which starts closed and remains offscreen, so the user sees no navigation to the target.

Open the responsive drawer when a batch jump switches to the music list.
</issue_to_address>

### Comment 4
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="55" />
<code_context>
+  // 等 VList 挂载的这段时间里列表可能已经变了,下标要按最新数据重算
+  const idx = displayedMusic.value.findIndex(m => m.id === musicId);
+  if (idx === -1) return;
+  list.scrollToIndex(idx, { align: 'center', smooth: true });
+};
+
</code_context>
<issue_to_address>
**Selection and viewport diverge**

When a user selects another row while a batch jump is waiting for the list to mount, `scrollToMusic` waits for the list to mount, but a manual row click only changes `selectMusicId` and does not invalidate `lastJump`. The pending continuation then scrolls to the earlier batch target while a different song remains selected, leaving the selected song out of view.

Invalidate pending jumps on manual row selection, or check that the target is still selected before scrolling.
</issue_to_address>

Sourcery 评估

等待批准。 请先处理 4 项发现。

阻塞性发现:MaiChartManager/Front/src/views/Charts/index.tsx:76、MaiChartManager/Front/src/views/Charts/index.tsx:78、MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:40、MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:55


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="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="76" />
<code_context>
+                <div class="flex flex-wrap items-center justify-end gap-2 max-[639px]:w-full">
+                  {!!selectedMusic.value && <div class="shrink-0"><CopyToButton /></div>}
+                  {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+                  <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
                     <div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
                     <div class="shrink-0"><ImportCreateChartButton /></div>
</code_context>
<issue_to_address>
**A000 remains editable by keyboard**

When A000 is selected and a user activates a wrapped control with the keyboard or presses Ctrl+S, the `pointer-events-none` wrapper does not disable its child buttons or guard their handlers, so `DeleteMusic` and import/create actions can still modify the read-only directory; the mounted save shortcut can also run through Ctrl+S.

Disable or guard the wrapped actions and save shortcut whenever A000 is selected.
</issue_to_address>

### Comment 2
<location path="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="78" />
<code_context>
+                  {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+                  <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
                     <div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
                     <div class="shrink-0"><ImportCreateChartButton /></div>
-                  </>
-                )}
</code_context>
<issue_to_address>
**Open create dialog targets A000**

When a create dialog is open in an editable directory and another tab changes the shared selected directory to A000 before confirmation, `ImportCreateChartButton` stays mounted when the selected directory changes, so its already-open create dialog remains active. `CreateMusicModal.save` reads the live `selectedADir` rather than the directory where the dialog was opened; confirming after another tab changes the shared directory selection to A000 calls `AddMusic` for A000, which the controller accepts.

Capture the directory when opening the dialog and reject or close the dialog if that directory is A000; also enforce the read-only rule in the mutation endpoint.
</issue_to_address>

### Comment 3
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="40" />
<code_context>
+  if (!musicList.value.some(m => m.id === musicId)) return;
+
+  // 跳转的含义是「回到曲目列表并看到这首歌」:左侧停在目录管理时也要切回列表
+  if (leftPanel.value !== 'musicList') leftPanel.value = 'musicList';
+
+  // 搜索词把目标挡住了就先清掉,否则 scrollToIndex 找不到它
</code_context>
<issue_to_address>
**Jump target stays hidden**

When a batch-page jump runs below 1440px, `scrollToMusic` switches `leftPanel` to `musicList` but does not open the responsive drawer, which starts closed and remains offscreen, so the user sees no navigation to the target.

Open the responsive drawer when a batch jump switches to the music list.
</issue_to_address>

### Comment 4
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="55" />
<code_context>
+  // 等 VList 挂载的这段时间里列表可能已经变了,下标要按最新数据重算
+  const idx = displayedMusic.value.findIndex(m => m.id === musicId);
+  if (idx === -1) return;
+  list.scrollToIndex(idx, { align: 'center', smooth: true });
+};
+
</code_context>
<issue_to_address>
**Selection and viewport diverge**

When a user selects another row while a batch jump is waiting for the list to mount, `scrollToMusic` waits for the list to mount, but a manual row click only changes `selectMusicId` and does not invalidate `lastJump`. The pending continuation then scrolls to the earlier batch target while a different song remains selected, leaving the selected song out of view.

Invalidate pending jumps on manual row selection, or check that the target is still selected before scrolling.
</issue_to_address>

Sourcery assessment

Approval pending. 4 findings to address first.

Blocking findings: MaiChartManager/Front/src/views/Charts/index.tsx:76, MaiChartManager/Front/src/views/Charts/index.tsx:78, MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:40, MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:55


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

浏览器审查指出 pointer-events-none 只挡鼠标:A000 下用 Tab 聚焦后回车、或按 Ctrl+S,
仍能触发导入/保存,会往游戏本体目录写数据。原来的「禁用」只有视觉效果。

- 新增 store.refs 的 isReadOnlyDir(selectedADir === 'A000'),所有写入口先看它
- 保存:按钮 disabled,save() 里再判一次(Ctrl+S 走的就是这个函数)
- 删除 / 导入 / 创建:按钮 disabled + 入口函数早退
- 替换谱面(ChartPanel 两个入口)与封面替换(JacketBox,含拖拽路径):入口早退
- 创建曲目对话框在确认时重新判目录,堵住「打开后别处切到 A000 再确认」的竞态

验证:A000 下四个按钮均为 disabled,模拟 Ctrl+S 与点击均未产生任何写请求(用一次
性 mock 观察,未打真实后端);pnpm typecheck / build 通过。

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 列表里的 ID 是补零的 6 位显示(000123),但搜索只匹配原始数值,搜 000123 无结果;
  matchMusicKeyword 现在同时匹配补零形式(批量操作页与工具页搜索一并修好)
- 窄屏(<1440)下列表是抽屉:批量跳转只切了 leftPanel,抽屉仍关着,用户看不到目标;
  抽屉状态移到 views/Charts/refs.ts,跳转时一并拉开
- 等待列表挂载期间用户手动点了别的曲目时,之前那次跳转不再作数(避免选中的是 A、
  列表滚到 B)

验证:mock 数据下 000123→标准谱、010123→DX 谱、123→两者;typecheck / build 通过。

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)

@cubic-dev-ai cubic-dev-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.

All reported issues were addressed across 13 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

审查指出:入口的只读判断和真正的写入之间隔着若干异步步骤(选文件、确认弹窗、逐条处理),
而写入时又读了一次 selectedADir。期间目录被切到 A000,写操作就会落到只读目录。

- 导入:startProcess 入口快照 importDir,之后 AddMusic / ImportChart / SetAudio /
  SetAudioPreview / SetMusicJacket / SetMovie / 回滚用的 DeleteMusic 全部用它
- 替换谱面:prepareReplaceChart 时快照 replaceDir,确认后 ReplaceChart 用它

这样「中途切目录」只会影响下一次导入,不会把正在进行的那次带偏。

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- JacketBox 的 SetMusicJacket 原来传的是「当前选中目录」;选文件是异步的,期间切目录会
  写错地方。改成传曲目自己的 assetDir(本来也该如此)
- A000 下封面不再显示 cursor-pointer:现在点了没反应,手型会让人以为操作失败
- musicDrawerOpen 是模块级状态,切到别的侧栏页再回来会保留;Charts 卸载时复位

AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
cubic-dev-ai[bot]
cubic-dev-ai Bot previously approved these changes Nov 1, 2026

@cubic-dev-ai cubic-dev-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.

0 issues found across 4 files (changes from recent commits).

Auto-approved: 谱面管理前端改动:列表新增搜索与空态、修复 1440 断点,并将 A000 只读统一收敛到保存/删除/导入/替换/换封面等写入入口;改动有界、降低误写风险,memo 写入属旁注文件,不在本次只读范围内。

View guided diff | Re-trigger cubic

@Misaka-0x447f
Misaka-0x447f requested a review from clansty November 1, 2026 09:59
- 绝对定位居中改用 top-0 bottom-0 + my-auto,不再依赖 -translate-y-1/2

- 原因:@munet/ui 全局的 button:hover { transform: scale(1.02) } 特异性更高,会把 UnoCSS 的 translate 整条顶掉,导致悬停时按钮向下掉半个高度
- innerClass 里的 pr-10 改用 pr-10!

- 原因:MuNET-UI TextInput 的 .wrapper input 选择器特异性 0,1,1 高于普通工具类,原来的 padding-right 没生效,长关键词尾部会被清空按钮盖住
- saveMusicIfNeeded 由窗口失焦和切歌自动触发,原来用 selectedADir,切目录后会写错目录

- 增加 A000 拦截,避免待保存的内容被写进只读目录
- ChartPanel 的 sync 直接调 EditChart* 接口,之前只靠输入组件的 disabled 注入

- 入口处补 isReadOnlyDir 判断,与保存 / 删除 / 导入的处理保持一致
- 在清空按钮上阻止 mousedown 的默认行为,焦点不会离开输入框

- 否则点完清空焦点落在按钮上,想接着打字还得再点一次输入框

@cubic-dev-ai cubic-dev-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.

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread MaiChartManager/Front/src/store/refs.ts Resolved
@cubic-dev-ai
cubic-dev-ai Bot dismissed their stale review November 1, 2026 18:26

Dismissed because Cubic found issues in a newer review.

- 同 ID 曲目可能同时存在于 A000 和用户目录,按 id 直接 find 会命中别目录那条,modified 为 false 导致自动保存被误判成不需要保存、直接跳过

- 查询加上 modified 条件定位真正被编辑的条目,资产目录仍取曲目自身的 assetDir

@cubic-dev-ai cubic-dev-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.

0 issues found across 1 file (changes from recent commits).

Requires human review: 谱面管理改动(列表搜索、1440 断点、空态、A000 写入拦截)整体有界,但 A000 只读边界是否应覆盖 memo 写入仍未解决,需人确认该数据写入策略。

View guided diff | Re-trigger cubic

@clansty
clansty merged commit 498d3e4 into MuNET-OSS:main Nov 1, 2026
3 checks passed
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.

2 participants