204 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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 锁定 SonnerCONTEXT 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 非 067 条存量错误,与本 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>