diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index c2e411de..bcee64c9 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -200,6 +200,14 @@ - 验证:`npm test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "puzzle form checks mud points before creating a draft|match3d form checks mud points before creating a draft|bark battle form checks mud points before creating image assets"` 应断言弹窗出现、对应工作台仍在、玩法模板分类不再出现。 - 关联:`src/components/platform-entry/PlatformEntryFlowShellImpl.tsx`、`src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx`、`docs/【玩法创作】平台入口与玩法链路-2026-05-15.md`。 +## 内嵌泥点确认弹窗必须自带平台主题作用域 + +- 现象:拼图 / 抓大鹅统一创作页点击生成后,“确认消耗泥点”弹窗正文和按钮存在,但弹窗面板背景透明,只剩遮罩和文字。 +- 原因:`PlatformMudPointConfirmDialog` 作为二级确认常以 `portal={false}` 内嵌到工作台局部 DOM,局部节点不一定继承 `.platform-theme`;`platform-modal-shell` 依赖 `--platform-modal-fill` 等主题变量,变量缺失时面板底色解析为空。 +- 处理:共享泥点确认弹窗默认在 overlay 上带 `platform-theme platform-theme--`、`platform-modal-backdrop` 和实色遮罩,在 panel 上带 `platform-modal-shell platform-remap-surface`;单按钮状态弹窗也要有默认 light 主题,避免未来独立调用复现。 +- 验证:浏览器触发 `/creation/puzzle` 与 `/creation/match3d` 的泥点确认弹窗,检查 overlay 最近主题 class 存在、`--platform-modal-fill` 有值且面板为实底;聚焦测试覆盖默认 overlay / panel class。 +- 关联:`src/components/common/PlatformMudPointConfirmDialog.tsx`、`src/components/common/PlatformStatusDialog.tsx`、`src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx`、`src/components/unified-creation/workspaces/Match3DCreationWorkspace.tsx`。 + ## 玩法入口分类字段缺失要前端兜底 - 现象:平台创作入口初始化时,`platformEntryCreationTypes.ts` 直接对 `creationTypes[].categoryId` / `categoryLabel` 调 `trim()`,一旦后端旧数据、局部 mock 或异常返回里缺字段,整个创作页会在 `derivePlatformCreationTypes(...)` 里直接炸掉。 diff --git a/src/components/common/PlatformMudPointConfirmDialog.test.tsx b/src/components/common/PlatformMudPointConfirmDialog.test.tsx index f9b21200..58984f1d 100644 --- a/src/components/common/PlatformMudPointConfirmDialog.test.tsx +++ b/src/components/common/PlatformMudPointConfirmDialog.test.tsx @@ -51,3 +51,27 @@ test('supports extra detail copy and close button override', () => { expect(within(dialog).getByText('本次会覆盖当前待确认素材。')).toBeTruthy(); expect(screen.queryByRole('button', { name: '关闭' })).toBeNull(); }); + +test('applies the stronger default overlay and panel chrome', () => { + render( + {}} + onConfirm={() => {}} + portal={false} + />, + ); + + const dialog = screen.getByRole('dialog', { name: '确认消耗泥点' }); + const overlay = dialog.parentElement as HTMLElement; + + expect(overlay.className).toContain('platform-modal-backdrop'); + expect(overlay.className).toContain('platform-theme--light'); + expect(overlay.className).toContain('!bg-black/45'); + expect(dialog.className).toContain('platform-modal-shell'); + expect(dialog.className).toContain('max-w-xs'); + expect(dialog.className).toContain( + 'shadow-[0_24px_70px_rgba(15,23,42,0.22)]', + ); +}); diff --git a/src/components/common/PlatformMudPointConfirmDialog.tsx b/src/components/common/PlatformMudPointConfirmDialog.tsx index 198ead2e..cbbb3918 100644 --- a/src/components/common/PlatformMudPointConfirmDialog.tsx +++ b/src/components/common/PlatformMudPointConfirmDialog.tsx @@ -1,5 +1,6 @@ import type { ReactNode } from 'react'; +import { useAuthUi } from '../auth/AuthUiContext'; import { UnifiedConfirmDialog } from './UnifiedConfirmDialog'; type PlatformMudPointConfirmDialogProps = { @@ -19,6 +20,14 @@ type PlatformMudPointConfirmDialogProps = { panelClassName?: string; }; +const DEFAULT_OVERLAY_CLASS_NAME = 'platform-modal-backdrop z-[80] !bg-black/45'; +const DEFAULT_PANEL_CLASS_NAME = + 'platform-modal-shell platform-remap-surface max-w-xs rounded-[1.35rem] shadow-[0_24px_70px_rgba(15,23,42,0.22)]'; + +function joinClassNames(...classNames: Array) { + return classNames.filter(Boolean).join(' '); +} + /** * 平台泥点消耗确认弹窗。 * 统一承接“确认消耗泥点 + 消耗 N 泥点”的标准确认壳层,业务侧只保留点数与确认动作。 @@ -39,6 +48,8 @@ export function PlatformMudPointConfirmDialog({ overlayClassName, panelClassName, }: PlatformMudPointConfirmDialogProps) { + const resolvedPlatformTheme = useAuthUi()?.platformTheme ?? 'light'; + return (
消耗 {points} 泥点
diff --git a/src/components/common/PlatformStatusDialog.test.tsx b/src/components/common/PlatformStatusDialog.test.tsx index 0533b494..7e9b37b9 100644 --- a/src/components/common/PlatformStatusDialog.test.tsx +++ b/src/components/common/PlatformStatusDialog.test.tsx @@ -21,6 +21,7 @@ test('renders result state with description and primary action', () => { ); const dialog = screen.getByRole('dialog', { name: '支付成功' }); + const overlay = dialog.parentElement as HTMLElement; const badge = dialog.querySelector('.platform-icon-badge'); const action = screen.getByRole('button', { name: '知道了' }); const visibleDescription = dialog.querySelector( @@ -28,6 +29,8 @@ test('renders result state with description and primary action', () => { ); expect(dialog).toBeTruthy(); + expect(overlay.className).toContain('platform-theme--light'); + expect(overlay.className).toContain('platform-profile-modal-overlay'); expect(visibleDescription?.textContent).toBe('账户状态已刷新'); expect(badge?.className).toContain('text-[var(--platform-success-text)]'); expect(action.className).toContain('platform-primary-button'); @@ -97,6 +100,27 @@ test('supports custom badge icon label and action button styling', () => { expect(action.className).toContain('bg-slate-950'); }); +test('keeps default theme classes when callers customize the overlay', () => { + render( + , + ); + + const dialog = screen.getByRole('dialog', { name: '发布失败' }); + const overlay = dialog.parentElement as HTMLElement; + + expect(overlay.className).toContain('platform-theme--light'); + expect(overlay.className).toContain('platform-profile-modal-overlay'); + expect(overlay.className).toContain('bg-slate-950/58'); + expect(overlay.className).toContain('custom-overlay'); +}); + test('supports header notice layout with body content and close button', () => { const onClose = vi.fn(); diff --git a/src/components/common/PlatformStatusDialog.tsx b/src/components/common/PlatformStatusDialog.tsx index af74622d..7b95ef02 100644 --- a/src/components/common/PlatformStatusDialog.tsx +++ b/src/components/common/PlatformStatusDialog.tsx @@ -64,11 +64,15 @@ type PlatformStatusVisualConfig = { }; const DEFAULT_OVERLAY_CLASS = - 'platform-profile-modal-overlay bg-slate-950/72 backdrop-blur-xl'; + 'platform-theme platform-theme--light platform-profile-modal-overlay bg-slate-950/72 backdrop-blur-xl'; const DEFAULT_PANEL_CLASS = 'platform-remap-surface !max-w-sm rounded-[1.4rem]'; const DEFAULT_BODY_CLASS = 'px-5 pb-5 pt-6 text-center'; +function joinClassNames(...classNames: Array) { + return classNames.filter(Boolean).join(' '); +} + function getStatusVisualConfig( status: PlatformStatusDialogStatus, ): PlatformStatusVisualConfig { @@ -119,7 +123,7 @@ export function PlatformStatusDialog({ closeLabel, closeDisabled = false, zIndexClassName = 'z-[90]', - overlayClassName = DEFAULT_OVERLAY_CLASS, + overlayClassName, panelClassName = DEFAULT_PANEL_CLASS, bodyClassName = DEFAULT_BODY_CLASS, iconClassName, @@ -144,7 +148,10 @@ export function PlatformStatusDialog({ portal={false} size="sm" zIndexClassName={zIndexClassName} - overlayClassName={overlayClassName} + overlayClassName={joinClassNames( + DEFAULT_OVERLAY_CLASS, + overlayClassName, + )} panelClassName={panelClassName} bodyClassName={bodyClassName} >