From f5e49ef9bd93c3d8db5f3776d2236f793827c840 Mon Sep 17 00:00:00 2001
From: thzxx <1440196015@qq.com>
Date: Mon, 24 Aug 2026 21:53:04 +0800
Subject: [PATCH] =?UTF-8?q?v0.1.36a:=20=E4=B8=89=E9=97=AE=E9=A2=98?=
=?UTF-8?q?=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94=E6=88=90=E5=91=98=E5=BC=B9?=
=?UTF-8?q?=E6=A1=86=E5=8F=8C=E6=8E=92=E5=90=8D/=E7=95=8C=E9=9D=A2?=
=?UTF-8?q?=E7=99=BD=E5=B1=8F=E9=98=B2=E7=BA=BF/=E6=8E=A8=E8=BF=9B?=
=?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=8F=96=E8=AF=81+=E5=87=BA=E9=99=A9?=
=?UTF-8?q?=E4=BF=9D=E9=9A=9C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
【Bug2 双排名】MemberModal 顶部与 ModalShell 壳顶重复渲染 (title+category 各两排)
→ children 内嵌 title/cat 移除(壳已承载),改 mm-brief 一行(寿数/根骨)
【Bug3 定制白屏】App 无错误边界 → 全树崩溃只剩背景
→ AppErrorBoundary 一级防线(显示错误信息+重新加载按钮,不再白屏)
【Bug1 推进崩溃】advance 异常 toast 原文只写“已保存”——实为 Stash 静默吞错+双保险缺失
→ ①toast 显示根错前 120 字(玩家可见——此前只有 console) ②出险保存双保险:
Stash 失败时 localStorage 应急副本+明示“存储也失败”提示手动存档 ③speed timer 内 advance().catch 隔离
【验证】tsc 0 错/build 绿/7000 测试绿
---
src/renderer/App.tsx | 23 ++++++++++++++++++++++
src/renderer/main.tsx | 6 ++++--
src/renderer/ui/components/MemberModal.tsx | 8 ++------
src/renderer/ui/store.ts | 23 ++++++++++++++++++++--
4 files changed, 50 insertions(+), 10 deletions(-)
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index 84664a8..bc2a753 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -12,6 +12,29 @@ import { startFxLayer, spawnBurst, stopFxLayer } from './ui/fx-canvas'
import { DriftLayer } from './ui/components/DriftLayer'
import { seasonOf } from './game/data/season'
+import { Component, type ReactNode, type ErrorInfo } from 'react'
+
+interface EBState { err: Error | null }
+export class AppErrorBoundary extends Component<{ children: ReactNode }, EBState> {
+ state: EBState = { err: null }
+ static getDerivedStateFromError(err: Error): EBState { return { err } }
+ componentDidCatch(err: Error, info: ErrorInfo): void {
+ console.error('[ui-crash]', err, info.componentStack)
+ }
+ render(): ReactNode {
+ if (this.state.err) {
+ return (
+
+
界面出错了(游戏数据未受影响)
+
{String(this.state.err?.message ?? this.state.err)}
+
+
+ )
+ }
+ return this.props.children
+ }
+}
+
export default function App() {
const screen = useGameStore((s) => s.screen)
const pendingEventId = useGameStore((s) => s.pendingEventId)
diff --git a/src/renderer/main.tsx b/src/renderer/main.tsx
index 3ef95ad..e09b03c 100644
--- a/src/renderer/main.tsx
+++ b/src/renderer/main.tsx
@@ -1,13 +1,15 @@
import React from 'react'
import { createRoot } from 'react-dom/client'
-import App from './App'
+import App, { AppErrorBoundary } from './App'
import './ui/styles.css'
const root = document.getElementById('root')
if (root) {
createRoot(root).render(
-
+
+
+
)
}
diff --git a/src/renderer/ui/components/MemberModal.tsx b/src/renderer/ui/components/MemberModal.tsx
index 058f7d6..b109441 100644
--- a/src/renderer/ui/components/MemberModal.tsx
+++ b/src/renderer/ui/components/MemberModal.tsx
@@ -82,12 +82,8 @@ export function MemberModal({ member }: { member: Character }) {
>
}
>
-
- {member.name}
- {!member.alive && · 殁}
-
-
- 第{member.generation}代 · {member.gender === 'male' ? '男' : '女'} · {w.ageOf(member)}岁 · 寿数约{lifespan} · {describeRoots(member.roots)}
+
+ 寿数约 {lifespan} · {describeRoots(member.roots)}
diff --git a/src/renderer/ui/store.ts b/src/renderer/ui/store.ts
index 56e09dd..b2372ac 100644
--- a/src/renderer/ui/store.ts
+++ b/src/renderer/ui/store.ts
@@ -300,7 +300,26 @@ export const useGameStore = create((set, get) => ({
} catch {
// 保存失败时不再叠加错误
}
- setNextToast('时日流转出现异常,已保存出险现场,可回档重来。')
+ const detail = String(e).slice(0, 120)
+ let safed = false
+ if (st.world) {
+ try {
+ const file = await getSaveSlot(st.slot)
+ await file.open()
+ st.world.syncRng()
+ await file.saveState(st.world.state, '出险前')
+ const meta = metaFromState(st.world.state, st.slot)
+ await file.setMeta(meta)
+ safed = true
+ } catch (serr) {
+ console.error('[emergency-save-failed]', serr)
+ try {
+ localStorage.setItem('cotyc-emergency', JSON.stringify({ at: new Date().toISOString(), state: st.world.state }))
+ } catch { /* 极端环境不管 */ }
+ }
+ }
+ setNextToast(safed ? `时日流转异常(${detail})——出险现场已存,可回档。` : `时日流转异常(${detail})——【存储也失败】档未落盘,请手动存档后重开。`)
+ console.error('[advance-crash-detail]', detail)
stopTimer()
} finally {
set({ advancing: false })
@@ -411,7 +430,7 @@ export const useGameStore = create((set, get) => ({
// 速度被暂停/事件待决/战报展开/在途推进时静默轮空
return
}
- void cur.advance()
+ cur.advance().catch((e) => console.error('[speed-tick]', e))
}, ms)
set({ speed: v, timer })
},