美化 Galgame 模式切换并优化切换反馈#1192
Conversation
- N.E.K.O/plugin/plugins/galgame_plugin/static/style.css:1923
- 拆分 .mode-switch / .speed-switch 共享样式
- 为模式切换新增滑动指示器 ::before
- 增加三种模式的颜色区分、hover、active、focus-visible 微交互
- 响应式下 mode-switch 保持横向,speed-switch 仍可纵向
- N.E.K.O/plugin/plugins/galgame_plugin/static/main.js:3208
- renderStatus() 更新 #modeSwitch 的 data-active
- 根据当前 active 按钮设置 --indicator-left 和 --indicator-width
验证:
- node --check plugin\plugins\galgame_plugin\static\main.js 通过
- git diff --check 通过
- CSS {} 数量平衡检查通过:open=361, close=361
- 文档要求的 mode-switch / speed-switch 规则结构检查通过
- 滑块位置/高亮立即切过去
- summaryText 里的“模式:...”立即切到新模式
- 保存成功后仍会主动 refreshAll(... forceRefresh: true),用服务端状态校正
具体做法:
- 新增 updateSummaryMode(mode),如果已有 latestStatus,就复用原来的 buildStatusSummaryText({...latestStatus, mode})。
- 如果还没有状态,就只显示现有 i18n 的:
ui.summary.mode_part
ui.mode_label.*
- 没有新增硬编码提示文案,保存提示仍走现有:
ui.flash.saving_settings
ui.flash.settings_saved
ui.flash.auto_saving_settings
ui.flash.settings_auto_saved
验证:
- node --check plugin\plugins\galgame_plugin\static\main.js 通过
- git diff --check 通过
Walkthrough 喵为 galgame_plugin 前端引入待处理模式选择队列与对应 UI 控制与样式变更:新增 Changes 喵模式切换 UX 与状态同步 喵
Sequence Diagram 喵sequenceDiagram
participant User
participant UI as "Mode UI"
participant Handler as "Event Handler"
participant State as "State Mgmt"
participant Server
User->>UI: 点击模式按钮
UI->>Handler: 派发点击事件
Handler->>State: 设置 pendingModeSelection
Handler->>UI: 调用 updateModeSwitchControl(mode)
Handler->>UI: 调用 updateSummaryMode(mode)
Note right of Handler: 保存由单独 Save 操作触发(由 modeSaveRequestId 跟踪)
Handler->>Server: (通过 Save) 发起 saveMode() 持久化请求
Server-->>State: 返回并更新最新状态
State->>Handler: renderStatus(status)(若与 pending 匹配则清除 pending)
Handler->>UI: 最终调用 updateModeSwitchControl(currentMode)
Estimated Code Review Effort 喵🎯 4 (Complex) | ⏱️ ~45 minutes喵 Poem 喵
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
💡 Codex ReviewN.E.K.O/plugin/plugins/galgame_plugin/static/main.js Lines 6815 to 6817 in 0c8d9a9 The click handler now sets ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
|
@CodeRabbit review |
✅ Actions performedReview triggered.
|
- 新增 clearPendingModeSelection(mode),统一清理 pendingModeSelection 并用 latestStatus 重新渲染 UI。
- saveMode() 的所有客户端校验早退路径现在都会先清理 pending mode:
- reader mode 无效
- OCR interval 越界
- OCR trigger mode 无效
- vision max image px 越界
- 原来的保存异常 catch 分支也改为复用同一个 helper,避免两套清理逻辑分叉。
这样 mode switch 点击后如果 saveMode() 因本地校验失败直接 return,UI 会立刻回到 backend 当前 mode,不会继续被 pendingModeSelection || statusMode 强制显示为未实际应用的模式。
验证已跑:
node --check N.E.K.O\plugin\plugins\galgame_plugin\static\main.js
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
三百六十五里路哟,走过春夏秋冬 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugin/plugins/galgame_plugin/static/main.js`:
- Around line 2957-2960: The mode switch can be clicked when the plugin is
unavailable, leaving a "fake success" UI; fix by making the control truly
disabled when not ready and guarding its click handler. Concretely: when setting
modeSwitchEl.dataset.ready = 'false' ensure updateModeSwitchControl renders a
disabled state (e.g., add a CSS disabled class/aria-disabled and remove pointer
events or set disabled attribute) and have the click handler (the function bound
where mode selection is processed) first check modeSwitchEl.dataset.ready ===
'true' and return early if not; also ensure clearPendingModeSelection uses
latestStatus to restore the correct selected/highlighted UI by resetting the
modeSwitchEl state when latestStatus === null so UI cannot remain stuck on an
incorrect selection. Reference: modeSwitchEl, updateModeSwitchControl,
clearPendingModeSelection, latestStatus and the click handler bound to
`#modeSwitch`.
In `@plugin/plugins/galgame_plugin/static/style.css`:
- Around line 1212-1216: The toast container block (the CSS rule with position:
fixed; top: 18px; right: 24px; width: min(420px, calc(100vw - 32px));) currently
uses z-index: 1000 which is the same stacking level as details.panel-fullscreen
and .modal; increase the toast z-index to a higher value (e.g. >= 2000 or 9999)
so it always sits above details.panel-fullscreen and .modal to prevent it being
covered or losing click events.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 93ec6389-4aad-4d9a-aea3-eabe07f4534e
📒 Files selected for processing (2)
plugin/plugins/galgame_plugin/static/main.jsplugin/plugins/galgame_plugin/static/style.css
… toast above modal
- updateModeSwitchControl(_, { ready: false }) 现在真正进入 disabled 视觉
+ 行为: 按钮 disabled / aria-disabled=true, 清空 active 与 indicator
CSS 变量, 跳过 getBoundingClientRect 布局计算
- mode-btn click handler 守卫 dataset.ready !== 'true' 时直接 return,
防止 disabled 按钮被绕过(键盘焦点 / 程序触发)
- renderPluginUnavailable 改用 updateModeSwitchControl('', { ready: false })
统一渲染, 并清掉 pendingModeSelection 防止 stale UI
- .flash-message z-index 1000 -> 1100, 让 toast 始终盖过 .modal 和
details.panel-fullscreen (二者都是 1000)
addresses CodeRabbit P1 (Project-N-E-K-O#1192) + Minor
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@chatgpt-codex-connector 这条已经在 8e03d52(PR 作者上一个 commit)修了:新增 |
|
To use Codex here, create an environment for this repo. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2d371d2eaa
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const currentMode = pendingModeSelection || statusMode; | ||
| document.getElementById('summaryText').textContent = buildStatusSummaryText({ | ||
| ...status, | ||
| mode: currentMode, | ||
| }); |
There was a problem hiding this comment.
Keep modeSelect in sync with optimistic mode while pending
When pendingModeSelection is set, renderStatus renders the UI summary/switch using currentMode but the form control is still synchronized from status.mode earlier in the function, so the visible mode and #modeSelect can diverge during stale refreshes. In that window, any later saveMode() call (for example from changing speed/push settings or autosave) will read the stale #modeSelect value and send the previous mode back to galgame_set_mode, unintentionally reverting the user's just-selected mode.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 85d79be — verified the race trace:
- 用户点 mode-btn →
modeSelect.value = NEW,pendingModeSelection = NEW,settingsDirty = true,saveMode()派发 - saveMode 成功后
settingsDirty = false; settingsSaveInFlight = false; await refreshAll(...) - During that await, 两个 flag 都已清掉 → 任何 polling refresh 抢跑 renderStatus 用 stale status,
syncSettingsValue("modeSelect", STALE)不再被shouldPreserveSettingsControls()挡 →#modeSelect.value悄悄回到 STALE - UI 因
pendingModeSelection仍显示 NEW, 但 form control 已 stale → 下次 autosave (任何控件触发) 读到 stale modeSelect → 静默 revert
修法:把 currentMode = pendingModeSelection || statusMode 的计算前移到 syncSettingsValue 之前,syncSettingsValue("modeSelect", currentMode),让 form control 永远跟可见 UI / 乐观状态一致。这样即便上面的 race window 命中,sync 进 modeSelect 的也是 NEW(pending 还在)而不是 STALE。
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugin/plugins/galgame_plugin/static/main.js (1)
5676-5744:⚠️ Potential issue | 🟠 Major | ⚡ Quick win模式切换的乐观更新还会被“部分成功”和“并发点击”打穿喵
- 如果
galgame_set_mode已成功,但后面的galgame_set_ocr_timing或galgame_set_llm_vision失败,Line 5739 这里的clearPendingModeSelection(mode)会把 UI 回滚到latestStatus里的旧模式;这时服务端实际模式已经变了,页面会短暂显示错误状态喵。- 另外现在 mode 按钮在保存中仍可继续点击,Line 6823-6836 会并发发出多次
saveMode();旧请求如果后落地,仍可能把用户最后一次点击覆盖成更早的模式,变成真实写错服务端状态的竞态喵。建议把“mode 是否已提交成功”单独记录,并给模式保存加一个递增请求序号:只有最新一次请求才能清 pending / 刷新;如果 mode 已提交而后续设置失败,应走
refreshAll(... forceRefresh: true)做服务端校正,而不是回滚到旧latestStatus喵。🐾 可参考的最小修补方向
+let modeSaveRequestId = 0; + async function saveMode({ auto = false } = {}) { + const requestId = ++modeSaveRequestId; + let modePersisted = false; clearSettingsAutosaveTimer(); const mode = document.getElementById('modeSelect').value; ... try { if (mode && (!latestStatus || latestStatus.mode !== mode)) { pendingModeSelection = mode; updateModeSwitchControl(mode); updateSummaryMode(mode); } settingsSaveInFlight = true; ... await callPlugin('galgame_set_mode', { mode, push_notifications: pushNotifications, advance_speed: advanceSpeed, reader_mode: readerMode, }); + modePersisted = true; await callPlugin('galgame_set_ocr_timing', { ... }); await callPlugin('galgame_set_llm_vision', { ... }); - await refreshAll({ preserveFlash: true, forceInsights: true, forceRefresh: true }); + if (requestId === modeSaveRequestId) { + await refreshAll({ preserveFlash: true, forceInsights: true, forceRefresh: true }); + } } catch (error) { - clearPendingModeSelection(mode); + if (requestId === modeSaveRequestId) { + if (modePersisted) { + await refreshAll({ preserveFlash: true, forceInsights: true, forceRefresh: true }); + } else { + clearPendingModeSelection(mode); + } + } setFlash(error instanceof Error ? error.message : String(error), 'error'); } finally { settingsSaveInFlight = false; updateSettingsDirtyHint(); } } document.querySelectorAll('#modeSwitch .mode-btn').forEach((btn) => { btn.addEventListener('click', () => { + if (settingsSaveInFlight) { + return; + } const modeSwitchEl = document.getElementById('modeSwitch'); if (!modeSwitchEl || modeSwitchEl.dataset.ready !== 'true') { return; }Also applies to: 6822-6837
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/plugins/galgame_plugin/static/main.js` around lines 5676 - 5744, The optimistic mode switch can be overwritten by later/partial failures because saveMode allows concurrent requests and always calls clearPendingModeSelection(mode) on error; fix by adding a monotonically incrementing request id (e.g., saveModeRequestCounter) and a per-request localRequestId in saveMode, set a flag modeCommitted when callPlugin('galgame_set_mode') resolves successfully, and only clear or update pendingModeSelection / call clearPendingModeSelection(mode) if localRequestId === current counter (so out‑of‑order responses are ignored); on error, if modeCommitted is true then call refreshAll({ forceRefresh: true, preserveFlash: true }) to reconcile with server instead of rolling back to latestStatus; also prevent concurrent clicks by blocking further saveMode calls while settingsSaveInFlight is true (or disable the mode control via updateModeSwitchControl) so only one active saveMode request exists at a time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@plugin/plugins/galgame_plugin/static/main.js`:
- Around line 5676-5744: The optimistic mode switch can be overwritten by
later/partial failures because saveMode allows concurrent requests and always
calls clearPendingModeSelection(mode) on error; fix by adding a monotonically
incrementing request id (e.g., saveModeRequestCounter) and a per-request
localRequestId in saveMode, set a flag modeCommitted when
callPlugin('galgame_set_mode') resolves successfully, and only clear or update
pendingModeSelection / call clearPendingModeSelection(mode) if localRequestId
=== current counter (so out‑of‑order responses are ignored); on error, if
modeCommitted is true then call refreshAll({ forceRefresh: true, preserveFlash:
true }) to reconcile with server instead of rolling back to latestStatus; also
prevent concurrent clicks by blocking further saveMode calls while
settingsSaveInFlight is true (or disable the mode control via
updateModeSwitchControl) so only one active saveMode request exists at a time.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: bcef78fb-7f50-4bd3-8ff4-ab376e8efdec
📒 Files selected for processing (2)
plugin/plugins/galgame_plugin/static/main.jsplugin/plugins/galgame_plugin/static/style.css
…rtial-success / concurrent saveMode race
Codex P1 (modeSelect divergence):
renderStatus 之前先 sync modeSelect = status.mode 再算 currentMode,
pending 时 modeSelect 与 UI / pendingModeSelection 错位 -> 后续任意
saveMode (autosave / speed / push) 读到 stale modeSelect 把旧 mode
发回 backend, 静默 revert 用户选择.
改成先算 currentMode (pending || status), 再 syncSettingsValue 用
currentMode, modeSelect 永远跟可见 UI 一致.
CodeRabbit outside-diff (partial-success + concurrent click):
- galgame_set_mode 已 commit 但后续 set_ocr_timing / set_llm_vision
抛错时, 旧 catch 走 clearPendingModeSelection(mode) 把 UI 回滚到
latestStatus.mode (旧), 与 backend 实际状态(已是 NEW) 不一致.
- 并发点击 mode A -> B 会 spawn 两个 saveMode, 后端响应顺序错时
backend 可能停在 A.
Fix: 加 modeSaveRequestId 单调序号, 每次 saveMode 取自己的 requestId
+ modeCommitted 标志.
- 成功路径 refreshAll 用 requestId === modeSaveRequestId 门控
- catch: stale 不动 UI; 非 stale 时 modeCommitted 走 forceRefresh
跟 backend 调和, 否则才 clearPendingModeSelection
- 早返回 validation 路径不动 (clearPendingModeSelection 自身已 mode-check 安全)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@coderabbitai outside-diff 那条 partial-success + concurrent click 已在 85d79be 修了,按你 diff 的思路:
没加点击 block —— autosave 在飞时阻止 mode-btn 是不好 UX,序号方案天然能让 stale 出顺序错也不破坏 UI。早返回 validation 路径没加序号守卫,因为 另外另一条 Codex 也指的相关问题( |
|
Tip For best results, initiate chat on the files or code changes.
单调序号 + 有一个小细节值得稍微留意一下喵——
总体来说,这个 commit 的修法已经把你们之前讨论的几个并发问题处理得很完整了喵。(……才、才不是在夸你们呢,只是客观评价而已喵!) (´• ω •`) *✲゚*。⋆ 🐇 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 85d79bedd9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
|
|
||
| async function saveMode({ auto = false } = {}) { | ||
| const requestId = ++modeSaveRequestId; |
There was a problem hiding this comment.
Increment save request ID after validation
modeSaveRequestId is incremented before any client-side validation, so a later invalid saveMode() call can supersede an earlier valid in-flight save without sending any request. In that case, the earlier save hits requestId !== modeSaveRequestId and skips its refreshAll reconcile path, leaving mode/summaries potentially stale until periodic auto-refresh even though settings were persisted. This is reproducible when users trigger another save action while one is running and a field (like OCR interval) is temporarily invalid.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 8317cc7 — ++modeSaveRequestId 移到 4 个 validation 早返回之后。
之前序号在第一行就 bump 的问题 trace:
- saveMode_A 进入,requestId=1,validation 通过,set_mode 已 commit,正在 await set_ocr_timing
- 用户在某个控件输入了非法 OCR interval(比如 0.1),autosave 或 click 触发 saveMode_B
- saveMode_B 入口
++modeSaveRequestId→ id=2,validation 失败(OCR interval 越界)→clearPendingModeSelection(mode); return;没发任何 server 请求 - saveMode_A 后续走到 success 路径
if (requestId === modeSaveRequestId)→ 1 !== 2 → 跳过 refreshAll - backend 实际已经 commit A 的所有设置,但前端 UI 不 reconcile,直到下次定时 auto-refresh
修法:把 const requestId = ++modeSaveRequestId; 挪到所有 validation 之后、try 之前。无效输入的早返回不再消耗序号,不会 starve 真正在飞的 valid save。
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugin/plugins/galgame_plugin/static/main.js (1)
5736-5759:⚠️ Potential issue | 🟠 Major | ⚡ Quick win并发保存下陈旧响应仍会覆盖最新 UI 状态喵
在 Line [5736]、Line [5755]、Line [5757],即使
requestId !== modeSaveRequestId,旧请求仍会写setFlash、settingsDirty、settingsSaveInFlight,会把新请求结果覆盖成错误提示或提前结束“保存中”反馈喵。建议把成功/失败/finally 的 UI 写入统一收敛到“仅最新请求可写”喵。🐾 建议最小修复喵
try { @@ - setFlash(auto ? uiT('ui.flash.settings_auto_saved', '设置已自动保存') : uiT('ui.flash.settings_saved', '设置已保存'), 'success'); - settingsDirty = false; - settingsSaveInFlight = false; - updateSettingsDirtyHint(); - if (requestId === modeSaveRequestId) { - await refreshAll({ preserveFlash: true, forceInsights: true, forceRefresh: true }); - } + if (requestId !== modeSaveRequestId) { + return; + } + setFlash(auto ? uiT('ui.flash.settings_auto_saved', '设置已自动保存') : uiT('ui.flash.settings_saved', '设置已保存'), 'success'); + settingsDirty = false; + settingsSaveInFlight = false; + updateSettingsDirtyHint(); + await refreshAll({ preserveFlash: true, forceInsights: true, forceRefresh: true }); } catch (error) { - if (requestId === modeSaveRequestId) { - if (modeCommitted) { - try { - await refreshAll({ preserveFlash: true, forceRefresh: true }); - } catch (refreshError) { - console.error('[galgame] mode save reconcile refresh failed', refreshError); - } - } else { - clearPendingModeSelection(mode); - } - } - setFlash(error instanceof Error ? error.message : String(error), 'error'); + if (requestId !== modeSaveRequestId) { + return; + } + if (modeCommitted) { + try { + await refreshAll({ preserveFlash: true, forceRefresh: true }); + } catch (refreshError) { + console.error('[galgame] mode save reconcile refresh failed', refreshError); + } + } else { + clearPendingModeSelection(mode); + } + setFlash(error instanceof Error ? error.message : String(error), 'error'); } finally { - settingsSaveInFlight = false; - updateSettingsDirtyHint(); + if (requestId === modeSaveRequestId) { + settingsSaveInFlight = false; + updateSettingsDirtyHint(); + } }Based on learnings: 在 Project-N-E-K-O/N.E.K.O 的 JavaScript 审查中,优先将问题集中在一条合并评论里按类别给出喵。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/plugins/galgame_plugin/static/main.js` around lines 5736 - 5759, Concurrent save handlers can let stale responses overwrite current UI state; guard all UI mutations so only the latest requestId (compare requestId === modeSaveRequestId) may change setFlash, settingsDirty, settingsSaveInFlight, and call updateSettingsDirtyHint/clearPendingModeSelection/refreshAll. Modify the try/catch/finally flow around the save logic so success branch, error branch, and finally branch each first check requestId === modeSaveRequestId (or early-return if not) before mutating those symbols (setFlash, settingsDirty, settingsSaveInFlight, updateSettingsDirtyHint) and only perform reconcile actions (modeCommitted handling, clearPendingModeSelection, refreshAll) when that check passes; keep existing logging for out-of-date responses but avoid UI writes from stale requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@plugin/plugins/galgame_plugin/static/main.js`:
- Around line 5736-5759: Concurrent save handlers can let stale responses
overwrite current UI state; guard all UI mutations so only the latest requestId
(compare requestId === modeSaveRequestId) may change setFlash, settingsDirty,
settingsSaveInFlight, and call
updateSettingsDirtyHint/clearPendingModeSelection/refreshAll. Modify the
try/catch/finally flow around the save logic so success branch, error branch,
and finally branch each first check requestId === modeSaveRequestId (or
early-return if not) before mutating those symbols (setFlash, settingsDirty,
settingsSaveInFlight, updateSettingsDirtyHint) and only perform reconcile
actions (modeCommitted handling, clearPendingModeSelection, refreshAll) when
that check passes; keep existing logging for out-of-date responses but avoid UI
writes from stale requests.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 3d2c43a4-2650-4252-afbb-ed996d31b40c
📒 Files selected for processing (1)
plugin/plugins/galgame_plugin/static/main.js
…e-request UI writes
Codex P2 (line 5677):
原来 ++modeSaveRequestId 在 client validation 之前 bump.
场景: saveMode_A valid 在飞 (id=1, mode 已 commit), 用户改 OCR
interval 为非法值, autosave/click 触发 saveMode_B, B 取 id=2 然后
validation 失败早返回 (无任何 server 请求). saveMode_A 后续 success
路径 requestId(1) !== modeSaveRequestId(2) -> 跳过 refreshAll, 即便
设置已持久化 UI 也不 reconcile, 直到下次定时刷新.
Fix: ++modeSaveRequestId 移到所有 validation 早返回之后.
CodeRabbit outside-diff (line 5736-5759):
原来 stale request 仍会写 setFlash / settingsDirty / settingsSaveInFlight
/ updateSettingsDirtyHint, 把最新 request 的 "保存中" / 成功消息
覆盖成自己的过期内容.
Fix: success 路径 await 全部完成后立即检查 stale, 是则 return (finally 自然走);
catch 头部检查 stale, 是则 console.error log 一下后 return, 不写 UI;
finally 整体 wrap 在 requestId === modeSaveRequestId 守卫里, latest
自己负责清 settingsSaveInFlight / 刷 hint, stale 不动.
这样并发 saveMode 无论先后完成, UI 状态都由最新一次单独负责.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@coderabbitai outside-diff(5736-5759,stale request 写 UI)已在 8317cc7 修了,按你 diff 思路完整收敛:
并行另一条 Codex P2 一并修了: |
|
Tip For best results, initiate chat on the files or code changes.
几个关键点都处理对了喵:
整体设计已经相当完整了喵~这几个 commit 层层递进把竞态和乐观更新的各种边界情况都覆盖到了,人家……人家就勉为其难地认可啦!(哼!) (`• ω •´) *✲゚*。⋆ 🐾 |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
plugin/plugins/galgame_plugin/static/main.js (1)
5676-5764:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift并发保存现在只防住了旧请求写 UI,没防住旧请求写后端喵
modeSaveRequestId只包住了成功/失败后的前端回写,但saveMode()里这 3 次callPlugin(...)还是会并发落到服务端喵。快速连点两个模式时,旧请求 A 仍然可能在新请求 B 之后把galgame_set_mode/galgame_set_ocr_timing/galgame_set_llm_vision写回去,最终把服务端状态覆盖成旧快照喵。更糟的是,A 如果已经成功提交了 mode,B 又在提交前失败,Line 5755-5757 还会回滚到latestStatus,而 A 因为在 Line 5736-5738 被判 stale 不会再refreshAll(),这样 UI 会显示旧模式,但服务端其实已经是 A 了喵。这里最好把整次保存串行化,或者改成单个原子保存接口,并在服务端按 requestId / version 丢弃旧写入喵;否则现在的 request guard 只能保证“旧请求别改 UI”,保证不了“旧请求别改配置”喵。
Also applies to: 6842-6856
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugin/plugins/galgame_plugin/static/main.js` around lines 5676 - 5764, The saveMode handler allows concurrent backend writes because the three callPlugin calls (galgame_set_mode, galgame_set_ocr_timing, galgame_set_llm_vision) are not tied to modeSaveRequestId, so an older request A can still overwrite a newer request B; fix by making the whole save atomic or serialized: either (A) create a single atomic endpoint (e.g. galgame_set_all_settings) and call that from saveMode so one request updates mode+ocr+vision together, or (B) serialize client saves by guarding all callPlugin invocations with a per-save lock/request token (use modeSaveRequestId/saveInFlightRequestId) and check the token before each callPlugin and abort remaining calls if stale, and ensure pendingModeSelection/latestStatus/refreshAll logic only runs for the current token; pick one approach and update saveMode, the call sites of callPlugin, and server handler to ignore/outdate requests by requestId if you implement server-side protection.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@plugin/plugins/galgame_plugin/static/main.js`:
- Around line 5676-5764: The saveMode handler allows concurrent backend writes
because the three callPlugin calls (galgame_set_mode, galgame_set_ocr_timing,
galgame_set_llm_vision) are not tied to modeSaveRequestId, so an older request A
can still overwrite a newer request B; fix by making the whole save atomic or
serialized: either (A) create a single atomic endpoint (e.g.
galgame_set_all_settings) and call that from saveMode so one request updates
mode+ocr+vision together, or (B) serialize client saves by guarding all
callPlugin invocations with a per-save lock/request token (use
modeSaveRequestId/saveInFlightRequestId) and check the token before each
callPlugin and abort remaining calls if stale, and ensure
pendingModeSelection/latestStatus/refreshAll logic only runs for the current
token; pick one approach and update saveMode, the call sites of callPlugin, and
server handler to ignore/outdate requests by requestId if you implement
server-side protection.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0a6d95ff-e003-4ba6-85da-8fbcc44bc366
📒 Files selected for processing (1)
plugin/plugins/galgame_plugin/static/main.js
|
@coderabbitai 这条 outside-diff(5676-5764, backend write ordering)我打算 不在这个 PR 里修,独立判断如下: 问题真实性:你的描述是对的—— 但 scope 不匹配:
建议作为单独 issue 跟,标 如果作者希望在这个 PR 里也修,再单独沟通。 |
|
Tip For best results, initiate chat on the files or code changes.
四点理由都站得住脚喵:
这个 PR 在 UI 层的乐观更新 + stale 写保护已经做得相当完整了,backend 写串行化完全可以作为独立 concern 单独跟进喵。 要人家帮你创建一个 GitHub Issue 来追踪这个 |
概要
验证
node --check plugin\plugins\galgame_plugin\static\main.jsgit diff --check -- plugin/plugins/galgame_plugin/static/style.css plugin/plugins/galgame_plugin/static/main.jsSummary by CodeRabbit
新功能
修复
样式