204 lines
7.4 KiB
Markdown
204 lines
7.4 KiB
Markdown
---
|
||
phase: 03-dialog-feedback
|
||
plan: 01
|
||
type: execute
|
||
wave: 1
|
||
depends_on: []
|
||
files_modified:
|
||
- app/layout.tsx
|
||
autonomous: true
|
||
requirements:
|
||
- CRED-FE-05
|
||
must_haves:
|
||
truths:
|
||
- "调用 toast.success(...) / toast.error(...) 后屏幕能看到 Sonner 通知"
|
||
- "Toaster 在所有路由下均可用(挂在 RootLayout 顶层)"
|
||
- "不破坏现有 RootLayout 渲染(children 仍正常显示)"
|
||
artifacts:
|
||
- path: "app/layout.tsx"
|
||
provides: "RootLayout 注入 <Toaster /> portal"
|
||
contains: "Toaster"
|
||
key_links:
|
||
- from: "app/layout.tsx"
|
||
to: "@/components/ui/sonner"
|
||
via: "import { Toaster }"
|
||
pattern: "from \"@/components/ui/sonner\""
|
||
---
|
||
|
||
<objective>
|
||
在 `app/layout.tsx` 的 `<body>` 内挂载 Sonner `<Toaster />`,让全局 `toast.success(...)` / `toast.error(...)` 命令式调用真正能在屏幕上显示。
|
||
|
||
**Purpose**:仓库 9 处 `toast(...)` 调用当前全部是 dead code(仅 `components/ui/sonner.tsx` 定义了 Toaster 包装但**从未挂载**),不挂载 Phase 3 的成功 / 失败反馈完全静默;这是 Phase 3 业务功能跑通的硬前置。
|
||
|
||
**Output**:修改后的 `app/layout.tsx`,新增 1 行 import + 1 个 JSX 元素挂载点。
|
||
</objective>
|
||
|
||
<execution_context>
|
||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||
</execution_context>
|
||
|
||
<context>
|
||
@.planning/PROJECT.md
|
||
@.planning/ROADMAP.md
|
||
@.planning/STATE.md
|
||
@.planning/phases/03-dialog-feedback/03-CONTEXT.md
|
||
@.planning/phases/03-dialog-feedback/03-RESEARCH.md
|
||
@CLAUDE.md
|
||
@app/layout.tsx
|
||
@components/ui/sonner.tsx
|
||
|
||
<interfaces>
|
||
<!-- Sonner Toaster 包装组件签名(从 components/ui/sonner.tsx 提取) -->
|
||
<!-- 直接 import 即可使用,无 props 也能渲染(已封装 next-themes 主题适配)。 -->
|
||
|
||
From components/ui/sonner.tsx:
|
||
```typescript
|
||
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||
const Toaster: ({ ...props }: ToasterProps) => JSX.Element
|
||
export { Toaster }
|
||
```
|
||
|
||
调用形态:`<Toaster />`(无 props 即可,theme 内部已读 next-themes context;本仓库未挂 ThemeProvider,会回退到默认 "system",无错误)
|
||
</interfaces>
|
||
</context>
|
||
|
||
<tasks>
|
||
|
||
<task type="auto">
|
||
<name>Task 1:在 RootLayout 挂载 Sonner Toaster</name>
|
||
<files>app/layout.tsx</files>
|
||
<read_first>
|
||
必读 `app/layout.tsx` 全文(仅 20 行)确认当前结构。当前内容:
|
||
|
||
```tsx
|
||
import type { Metadata } from 'next'
|
||
import './globals.css'
|
||
|
||
export const metadata: Metadata = {
|
||
title: 'v0 App',
|
||
description: 'Created with v0',
|
||
generator: 'v0.dev',
|
||
}
|
||
|
||
export default function RootLayout({
|
||
children,
|
||
}: Readonly<{
|
||
children: React.ReactNode
|
||
}>) {
|
||
return (
|
||
<html lang="en">
|
||
<body>{children}</body>
|
||
</html>
|
||
)
|
||
}
|
||
```
|
||
</read_first>
|
||
<action>
|
||
**精确改动 2 处**:
|
||
|
||
**改动 1**:在 `import './globals.css'` 之后追加 1 行 import:
|
||
|
||
```tsx
|
||
import { Toaster } from '@/components/ui/sonner'
|
||
```
|
||
|
||
**改动 2**:把 `<body>{children}</body>` 改为 `<body>{children}<Toaster /></body>`(即在 `{children}` 之后、`</body>` 之前插入 `<Toaster />`)。
|
||
|
||
**最终全文(应该是这样)**:
|
||
|
||
```tsx
|
||
import type { Metadata } from 'next'
|
||
import './globals.css'
|
||
import { Toaster } from '@/components/ui/sonner'
|
||
|
||
export const metadata: Metadata = {
|
||
title: 'v0 App',
|
||
description: 'Created with v0',
|
||
generator: 'v0.dev',
|
||
}
|
||
|
||
export default function RootLayout({
|
||
children,
|
||
}: Readonly<{
|
||
children: React.ReactNode
|
||
}>) {
|
||
return (
|
||
<html lang="en">
|
||
<body>
|
||
{children}
|
||
<Toaster />
|
||
</body>
|
||
</html>
|
||
)
|
||
}
|
||
```
|
||
|
||
**严格约束**:
|
||
- **不**挂 Radix Toast `<Toaster />`(来自 `@/components/ui/toaster`)—— 那是另一套实现,与 Sonner 不通信;本 phase 锁定 Sonner(CONTEXT D-Toast 决策)
|
||
- **不**新增 ThemeProvider / next-themes 包装 —— `components/ui/sonner.tsx:9` 有 `useTheme()` fallback 到 `"system"`,无 ThemeProvider 也能跑(本仓库目前确实没挂 ThemeProvider)
|
||
- **不**改 `metadata` / `html lang` / `globals.css` import 顺序
|
||
- **不**给 RootLayout 加 `"use client"` —— `<Toaster />` 自身就是 client component(`components/ui/sonner.tsx:1` 顶部已 `"use client"`),React Server Component 可以直接渲染 client child,无需 RootLayout 自己 client 化
|
||
- 这是本 phase 唯一改动 `app/layout.tsx` 的 task,**不**在此挂任何其他 provider
|
||
</action>
|
||
<verify>
|
||
<automated>
|
||
# Windows PowerShell(项目不含 .eslintrc*,lint 跳过沿用 Phase 1+2 判定)
|
||
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
|
||
|
||
# A 段:tsc 整体 + 反向断言(必须 0 条指向 app/layout.tsx)
|
||
npx tsc --noEmit 2>&1 | Select-String -Pattern 'app/layout\.tsx|app\\layout\.tsx'
|
||
# 期望:无任何输出(0 条新错误指向本文件)
|
||
|
||
# B 段:grep 验证 import + Toaster 元素都已落地(PowerShell Select-String)
|
||
Select-String -Path 'app/layout.tsx' -Pattern 'from "@/components/ui/sonner"'
|
||
# 期望:1 行命中 import 行
|
||
Select-String -Path 'app/layout.tsx' -Pattern '<Toaster\s*/>'
|
||
# 期望:1 行命中 <Toaster /> 标签
|
||
|
||
# C 段:lockfile 未动(不引入新依赖)—— Sonner 已在 deps(^1.7.1)
|
||
git diff --stat HEAD -- package.json yarn.lock package-lock.json pnpm-lock.yaml
|
||
# 期望:0 行(无 diff)
|
||
</automated>
|
||
</verify>
|
||
<done>
|
||
- `app/layout.tsx` 包含 `import { Toaster } from "@/components/ui/sonner"` 一行
|
||
- `<body>` 内 `{children}` 之后渲染 `<Toaster />`
|
||
- `npx tsc --noEmit` 输出过滤 `app/layout.tsx` 后 **0 条新错误**(67 条存量错误与本 task 无关)
|
||
- 4 个 manifest+lockfile 在 git diff 中 0 行 diff(不引入新依赖)
|
||
</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<verification>
|
||
**Phase 3 Plan 1 整体验证**(Plan 内已涵盖,此处汇总):
|
||
|
||
1. **类型检查**:`npx tsc --noEmit` exit 非 0(67 条存量错误,与本 phase 无关),但 `Select-String` 过滤 `app/layout.tsx` 命中 0 行
|
||
2. **挂载位置正确**:grep `<Toaster />` 命中且位于 `<body>` 内、`{children}` 之后(不是 `<head>` 内、不在 children 之前)
|
||
3. **不动 lockfile**:`git diff --stat HEAD -- package.json *.lock` 输出 0 行
|
||
4. **lint 跳过**:项目无 `.eslintrc*` / `eslint-config-next`,沿用 Phase 1+2 判定(不阻塞)
|
||
|
||
**关键失败模式**(如果出现,回头修):
|
||
- 如果挂在 `<html>` 之外或 `<head>` 内 → 渲染失败 → 修
|
||
- 如果误用 `import { Toaster } from "@/components/ui/toaster"`(Radix Toast)→ 与 Sonner toast() 不通信 → 修
|
||
- 如果给 layout.tsx 加了 `"use client"` → 改回 RSC(无必要)
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- [ ] `app/layout.tsx` import 块包含 `from "@/components/ui/sonner"`
|
||
- [ ] `app/layout.tsx` `<body>` 内含 `<Toaster />` 元素
|
||
- [ ] `npx tsc --noEmit` 过滤后 0 条新错误指向 `app/layout.tsx`
|
||
- [ ] 4 个 lockfile 在 git diff 中 0 行 diff
|
||
- [ ] Plan 03-02 的 toast 调用上线后能在屏幕显示(本 plan 单独无法跑通端到端,由 03-02 联动验证)
|
||
</success_criteria>
|
||
|
||
<output>
|
||
完成后创建 `.planning/phases/03-dialog-feedback/03-01-SUMMARY.md`,按 `$HOME/.claude/get-shit-done/templates/summary.md` 格式记录:
|
||
- 改动文件清单(1 个文件)
|
||
- import + JSX 两处具体行号
|
||
- tsc 过滤结果 + lockfile diff 结果
|
||
- 阻塞 / 非阻塞结论
|
||
- 下一步:执行 Plan 03-02
|
||
</output>
|