Skip to content

4.6.2#1002

Merged
MakinoharaShoko merged 59 commits into
mainfrom
dev
Jul 4, 2026
Merged

4.6.2#1002
MakinoharaShoko merged 59 commits into
mainfrom
dev

Conversation

@MakinoharaShoko

Copy link
Copy Markdown
Member

No description provided.

A-kirami and others added 30 commits June 8, 2026 15:08
Backlog 界面点击语音按钮时只调用 play(),未暂停其他
正在播放的 backlog 音频元素,导致多条语音同时播放。
同时未暂停游戏内正在播放的 currentVocal 语音,
点击 backlog 语音时与游戏内语音混合。

通过 querySelectorAll 匹配所有 backlog audio 元素,
在播放新语音前暂停并重置其他元素;同时暂停并重置
currentVocal 元素以避免与 backlog 语音混合。

Closes #866
Backlog 打开时自动播放不应推进剧情,否则在历史界面
点选 backlog 语音暂停 currentVocal 后,auto 会立即推进
到下一句对话并触发新的 vocal 播放,导致 Backlog 中听到
两条声音。

- autoPlay 在 Backlog 打开时直接 return,避免推进
- Backlog 点击 backlog 语音时同时 unmount vocal-play
  perform,清理外部 pause 留下的状态不一致

属于 #866 修复的延伸场景。
移除 'audio.id !== currentAudioId' 排除判断。后续逻辑会
重置并播放当前音频(currentTime = 0 + play()),故前置
pause 不需要排除当前。简化代码,无功能差异。

反馈来源:PR review
- Keep all text after the first unescaped semicolon when parsing inline comments, instead of only taking the first split segment.
- Add tests for normal inline comments and comment-only lines with multiple semicolons.
Treat empty setTransform content as an empty transform frame so
-writeDefault follows the same base default path as setTransform:{}.

Accept only object-shaped transform JSON for setTransform, changeFigure,
and changeBg. Non-object values from malformed or empty transform input
fall back to the default transform path instead of entering animation
frames.
MakinoharaShoko and others added 26 commits July 1, 2026 22:12
fix: #987 next 后接场景跳转失效问题
…ueries

feat: 增强编辑器预览 transform 查询协议与 overlay reference box 能力
Fix transform frame parsing for empty and non-object input
Prevent sync-scene from exposing reset-stage intermediate state while fast preview is settling.

The preview runtime now suppresses stage snapshots during sync-scene, only publishes the settled view state, and ignores stale sync-scene runs before they can commit over the latest preview target.
fix: conditionally enable video skip on double click
fix(parser): preserve semicolons in inline comments
…d-config

fix(parser): 修正资源去重和构建配置
…tion

fix: 修复 preview sync-scene 的中间 reset 状态暴露
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 4, 2026

Copy link
Copy Markdown

Deploying webgal-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6b4cbf6
Status: ✅  Deploy successful!
Preview URL: https://6e4b44bd.webgal-dev.pages.dev

View logs

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a flowchart feature to WebGAL, allowing players to track and jump to unlocked story nodes, and enhances the editor's live preview stability and protocol. It also refactors sentence advancement and fixes bugs in video playback, backlog audio, and comment parsing. The review feedback identifies several critical robustness issues, including potential runtime TypeErrors from unsafe property access on currentScene and node.data, unsafe string trimming in the transform parser, and an unconditional state commit when a forward step is blocked. Additionally, it suggests replacing the non-standard 'chinese' locale with 'zh-CN' for consistent time formatting.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +3 to +17
export function parseTransformFrame(raw: string): AnimationFrame | null {
const source = raw.trim();
if (source === '') return null;

try {
const parsed: unknown = JSON.parse(source);
return isTransformFrame(parsed) ? parsed : null;
} catch {
return null;
}
}

export function parseSetTransformFrame(raw: string): AnimationFrame | null {
return parseTransformFrame(raw.trim() === '' ? '{}' : raw);
}

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.

high

The functions parseTransformFrame and parseSetTransformFrame assume that the raw parameter is always a valid string. If raw is null, undefined, or of a non-string type, calling raw.trim() will throw a runtime TypeError. Adding a type guard to ensure raw is a string before trimming makes the parsing robust against unexpected inputs.

export function parseTransformFrame(raw: string | null | undefined): AnimationFrame | null {
  if (typeof raw !== 'string') return null;
  const source = raw.trim();
  if (source === '') return null;

  try {
    const parsed: unknown = JSON.parse(source);
    return isTransformFrame(parsed) ? parsed : null;
  } catch {
    return null;
  }
}

export function parseSetTransformFrame(raw: string | null | undefined): AnimationFrame | null {
  if (typeof raw !== 'string') return null;
  return parseTransformFrame(raw.trim() === '' ? '{}' : raw);
}

Comment on lines +104 to +105
forward();
commitForward();

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.

high

In continueSentence, commitForward() is called unconditionally after forward(). However, forward() can return false if the forward step is blocked by a scene write lock (WebGAL.sceneManager.lockSceneWrite). If forward() fails, calling commitForward() will commit an invalid or stale state, potentially leading to state desynchronization. Only call commitForward() if forward() returns true.

  if (forward()) {
    commitForward();
  }

Comment on lines +120 to +125
public unlockCurrentScene(refreshSnapshot = false) {
if (!this.hasFlowchart()) return;
const sceneNames = new Set([
normalizeSceneName(this.sceneManager.sceneData.currentScene.sceneName),
normalizeSceneName(this.sceneManager.sceneData.currentScene.sceneUrl),
]);

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.

medium

In unlockCurrentScene, this.sceneManager.sceneData.currentScene is accessed directly. During initialization or scene transitions, sceneData or currentScene might be undefined or null, which would cause a runtime crash. Adding a defensive check prevents potential TypeErrors.

  public unlockCurrentScene(refreshSnapshot = false) {
    if (!this.hasFlowchart()) return;
    const currentScene = this.sceneManager.sceneData?.currentScene;
    if (!currentScene) return;
    const sceneNames = new Set([
      normalizeSceneName(currentScene.sceneName),
      normalizeSceneName(currentScene.sceneUrl),
    ]);

Comment on lines +151 to +165
private createSnapshot(): ISaveData {
return {
nowStageState: cloneDeep(stageStateManager.getViewStageState()),
backlog: [],
index: -1,
saveTime: new Date().toLocaleDateString() + ' ' + new Date().toLocaleTimeString('chinese', { hour12: false }),
sceneData: {
currentSentenceId: this.sceneManager.sceneData.currentSentenceId,
sceneStack: cloneDeep(this.sceneManager.sceneData.sceneStack),
sceneName: this.sceneManager.sceneData.currentScene.sceneName,
sceneUrl: this.sceneManager.sceneData.currentScene.sceneUrl,
},
previewImage: '',
};
}

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.

medium

In createSnapshot, this.sceneManager.sceneData.currentScene is accessed directly, which can throw a TypeError if it is null or undefined. Additionally, 'chinese' is not a standard BCP 47 language tag for toLocaleTimeString and may cause inconsistent formatting or errors across different browser environments. It is safer to use 'zh-CN' and add optional chaining/nullish coalescing fallback values.

Suggested change
private createSnapshot(): ISaveData {
return {
nowStageState: cloneDeep(stageStateManager.getViewStageState()),
backlog: [],
index: -1,
saveTime: new Date().toLocaleDateString() + ' ' + new Date().toLocaleTimeString('chinese', { hour12: false }),
sceneData: {
currentSentenceId: this.sceneManager.sceneData.currentSentenceId,
sceneStack: cloneDeep(this.sceneManager.sceneData.sceneStack),
sceneName: this.sceneManager.sceneData.currentScene.sceneName,
sceneUrl: this.sceneManager.sceneData.currentScene.sceneUrl,
},
previewImage: '',
};
}
private createSnapshot(): ISaveData {
const currentScene = this.sceneManager.sceneData?.currentScene;
return {
nowStageState: cloneDeep(stageStateManager.getViewStageState()),
backlog: [],
index: -1,
saveTime: new Date().toLocaleDateString('zh-CN') + ' ' + new Date().toLocaleTimeString('zh-CN', { hour12: false }),
sceneData: {
currentSentenceId: this.sceneManager.sceneData?.currentSentenceId ?? 0,
sceneStack: cloneDeep(this.sceneManager.sceneData?.sceneStack ?? []),
sceneName: currentScene?.sceneName ?? '',
sceneUrl: currentScene?.sceneUrl ?? '',
},
previewImage: '',
};
}

Comment on lines +183 to +186
private currentSceneKey() {
const { currentScene } = this.sceneManager.sceneData;
return `${normalizeSceneName(currentScene.sceneName)}|${normalizeSceneName(currentScene.sceneUrl)}`;
}

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.

medium

In currentSceneKey, destructuring currentScene from this.sceneManager.sceneData will throw a runtime error if sceneData or currentScene is null/undefined. Adding optional chaining and a fallback ensures safety.

  private currentSceneKey() {
    const currentScene = this.sceneManager.sceneData?.currentScene;
    if (!currentScene) return '';
    return normalizeSceneName(currentScene.sceneName) + '|' + normalizeSceneName(currentScene.sceneUrl);
  }

Comment on lines +343 to +349
function getNodeWidth(node: IFlowchartNode, flowchartId: string, lockedNodeVisibility: LockedNodeVisibility) {
if (lockedNodeVisibility === 'node' && !WebGAL.flowchartManager.isUnlocked(flowchartId, node.id))
return NODE_MIN_WIDTH;
const label = node.data?.label || node.id;
const textWidth = Array.from(label).reduce((sum, char) => sum + (char.charCodeAt(0) > 255 ? 24 : 14), 0);
return Math.max(NODE_MIN_WIDTH, textWidth + 36);
}

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.

medium

In getNodeWidth, node.data?.label or node.id is passed directly to Array.from(). If either of these properties is not a string (e.g., a number or boolean from the JSON configuration), Array.from() will throw a runtime TypeError. Explicitly casting the label to a string first prevents this issue.

Suggested change
function getNodeWidth(node: IFlowchartNode, flowchartId: string, lockedNodeVisibility: LockedNodeVisibility) {
if (lockedNodeVisibility === 'node' && !WebGAL.flowchartManager.isUnlocked(flowchartId, node.id))
return NODE_MIN_WIDTH;
const label = node.data?.label || node.id;
const textWidth = Array.from(label).reduce((sum, char) => sum + (char.charCodeAt(0) > 255 ? 24 : 14), 0);
return Math.max(NODE_MIN_WIDTH, textWidth + 36);
}
function getNodeWidth(node: IFlowchartNode, flowchartId: string, lockedNodeVisibility: LockedNodeVisibility) {
if (lockedNodeVisibility === 'node' && !WebGAL.flowchartManager.isUnlocked(flowchartId, node.id))
return NODE_MIN_WIDTH;
const label = String(node.data?.label ?? node.id);
const textWidth = Array.from(label).reduce((sum, char) => sum + (char.charCodeAt(0) > 255 ? 24 : 14), 0);
return Math.max(NODE_MIN_WIDTH, textWidth + 36);
}

@MakinoharaShoko MakinoharaShoko merged commit e7f0abe into main Jul 4, 2026
2 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.

5 participants