Skip to content

初始化项目,实现首页和添加新书页的UI#1

Merged
lijianhua7 merged 4 commits into
mainfrom
setup-and-ui
Apr 1, 2026
Merged

初始化项目,实现首页和添加新书页的UI#1
lijianhua7 merged 4 commits into
mainfrom
setup-and-ui

Conversation

@lijianhua7

@lijianhua7 lijianhua7 commented Mar 31, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

发布说明

  • 新增功能

    • 完整项目框架与根布局、全局样式与字体
    • 首页:英雄区与书籍展示网格
    • “添加新书”页面:PDF/封面上传、语音选择与上传进度反馈
    • 顶部导航:路由高亮与身份验证控件
    • 书籍卡片、上传表单及一套表单/按钮/输入/标签等 UI 组件
    • 预置示例书籍与语音选项
  • 文档

    • 添加项目 README、Agent 指南与引用说明文件
  • 杂项

    • 添加默认忽略规则、构建/类型/ lint 与配置文件、类型声明与工具函数

@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

添加 Bookify 项目初始代码:项目配置、文档、全局样式与根布局、多项 UI 组件(导航、英雄区、书籍卡)、上传表单与表单原子组件、类型定义、常量与工具函数,以及 Clerk 中间件与 Next.js 配置。

Changes

Cohort / File(s) Summary
项目配置
\.gitignore, tsconfig.json, eslint.config.mjs, postcss.config.mjs, next.config.ts, components.json, package.json
新增项目级配置与依赖与忽略规则;Next.js images 远程模式、Shadcn UI 配置、ESLint/Tailwind/PostCSS、TypeScript 严格选项与 package 脚本。
文档
README.md, AGENTS.md, CLAUDE.md
新增 README 与 agents 指南/引用文件。
全局样式 与 根布局
app/globals.css, app/layout.tsx
新增大规模 Tailwind 全局样式(主题变量、暗色模式、Clerk 覆盖、UI 工具类)与根布局(字体、metadata、ClerkProvider、NavBar)。
页面路由
app/(root)/page.tsx, app/(root)/books/new/page.tsx
新增首页(Hero + 示例书列表)与“添加新书”页面(挂载 UploadForm)。
导航 与 中间件
components/NavBar.tsx, proxy.ts
新增客户端 NavBar(路由激活检测、Clerk 登录/用户按钮)与 Clerk 中间件及 matcher 配置。
核心 UI 组件
components/BookCard.tsx, components/HeroSection.tsx
新增书籍卡与英雄区组件(图片、链接、步骤卡)。
表单/上传 与 UI 原子组件
components/UploadForm.tsx, components/ui/form.tsx, components/ui/input.tsx, components/ui/label.tsx, components/ui/button.tsx
新增 UploadForm(Zod + react-hook-form、PDF/封面上传、语音选择、模拟提交)及表单构建块(FormField、FormLabel、FormControl、Input、Label、Button 等)。
常量、工具 与 类型
lib/constants.ts, lib/utils.ts, type.d.ts
新增品牌色与示例数据、文件/语音/ASSISTANT_ID 配置、Clerk 外观覆盖、cn 工具函数以及大量全局类型声明。

Sequence Diagram(s)

(未生成序列图。)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🐇 我是兔子,书页轻翻声,
新卷入库,封面泛金银。
组件列队,样式织成林,
表单上线,语音待唤醒。
Bookify 起航,阅读路正新。

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题清晰地总结了主要变更:初始化项目并实现首页和添加新书页的UI组件,与changeset内容相符。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch setup-and-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 10

🧹 Nitpick comments (9)
README.md (1)

1-36: README 与本 PR 目标不完全对齐,建议补充业务页面说明。

当前内容还是模板文案;建议在 Line [3] 后新增“项目简介/已实现页面”部分,至少写明首页与“添加新书页”的路由、主要交互和本地预览步骤,避免评审与后续接手人员误解交付范围。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 1 - 36, README 当前仍为模板文案,请在 Line 3 后新增一个“项目简介 /
已实现页面”段落,简要说明项目目的、已实现路由(例如 app/page.tsx 对应的首页和“添加新书”页面的路由如 /books/new 或
app/books/new/page.tsx)、每个页面的主要交互点(首页展示书目、添加新书表单字段与提交流程、表单校验要点),并补充本地预览步骤(运行 npm
run dev / pnpm dev 等以及访问 http://localhost:3000
及新增页面的路径);保持描述简短、可读,方便评审与后续接手人员快速了解交付范围。
components/HeroSection.tsx (1)

45-67: 步骤卡片可数据驱动渲染,减少重复 JSX。

这段结构重复较多,建议抽成数组并 map 渲染,后续改文案/增减步骤会更稳。

建议重构
 export default function HeroSection() {
+  const steps = [
+    { no: "1", title: "上传 PDF", desc: "添加你的书籍文件" },
+    { no: "2", title: "AI 处理", desc: "我们分析书籍内容" },
+    { no: "3", title: "语音聊天", desc: "与 AI 讨论书籍" },
+  ];
+
   return (
@@
-            {/* 步骤 1 */}
-            <div className="library-step-item">
-              <div className="library-step-number">1</div>
-              <div>
-                <p className="library-step-title">上传 PDF</p>
-                <p className="library-step-description">添加你的书籍文件</p>
-              </div>
-            </div>
-            {/* 步骤 2 */}
-            <div className="library-step-item">
-              <div className="library-step-number">2</div>
-              <div>
-                <p className="library-step-title">AI 处理</p>
-                <p className="library-step-description">我们分析书籍内容</p>
-              </div>
-            </div>
-            {/* 步骤 3 */}
-            <div className="library-step-item">
-              <div className="library-step-number">3</div>
-              <div>
-                <p className="library-step-title">语音聊天</p>
-                <p className="library-step-description">与 AI 讨论书籍</p>
-              </div>
-            </div>
+            {steps.map((step) => (
+              <div className="library-step-item" key={step.no}>
+                <div className="library-step-number">{step.no}</div>
+                <div>
+                  <p className="library-step-title">{step.title}</p>
+                  <p className="library-step-description">{step.desc}</p>
+                </div>
+              </div>
+            ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/HeroSection.tsx` around lines 45 - 67, The repeated step-card JSX
in HeroSection.tsx should be data-driven: create an array (e.g., const steps =
[{id: 1, title: '上传 PDF', desc: '添加你的书籍文件'}, ...]) and replace the three
repeated blocks with a single map over that array inside the HeroSection
render/return, rendering the same structure (library-step-item,
library-step-number, library-step-title, library-step-description) and using
step.id as the React key; this centralizes content so updating
titles/descriptions or adding/removing steps only requires editing the steps
array.
components/ui/form.tsx (1)

27-29: Context 默认值使用空对象类型断言可能导致运行时问题。

FormFieldContextFormItemContext 的默认值为空对象 {} as FormFieldContextValue,这会在组件未正确包裹时产生难以调试的问题。当前的错误检查(如上所述)无法正确捕获这种情况。

♻️ 建议使用 null 作为默认值
-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue
-)
+const FormFieldContext = React.createContext<FormFieldContextValue | null>(null)

-const FormItemContext = React.createContext<FormItemContextValue>(
-  {} as FormItemContextValue
-)
+const FormItemContext = React.createContext<FormItemContextValue | null>(null)

然后在 useFormField 中进行适当的 null 检查。

Also applies to: 48-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/form.tsx` around lines 27 - 29, FormFieldContext and
FormItemContext are created with unsafe "{} as ..." defaults which can mask
missing providers; change their default values to null and update useFormField
to check for null and throw a clear error if the hook is used outside the
provider (reference FormFieldContext, FormItemContext, and useFormField). Ensure
the context types allow null (e.g., FormFieldContextValue | null) and replace
any direct property access with a guard that throws a descriptive error so
consumers get an explicit runtime message when the provider is missing.
components/UploadForm.tsx (3)

93-95: 使用 document.getElementById 触发文件选择不是最佳 React 模式。

建议使用 useRef 来引用隐藏的 input 元素,这更符合 React 的声明式编程范式,也更易于测试和维护。

♻️ 建议使用 useRef

在组件顶部添加 ref:

const pdfInputRef = useRef<HTMLInputElement>(null);
const coverInputRef = useRef<HTMLInputElement>(null);

然后修改点击处理和 input 元素:

                     <div
                       className={cn(...)}
-                      onClick={() =>
-                        document.getElementById("pdf-upload")?.click()
-                      }
+                      onClick={() => pdfInputRef.current?.click()}
                     >
                       <input
-                        id="pdf-upload"
+                        ref={pdfInputRef}
                         type="file"

Also applies to: 161-163

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/UploadForm.tsx` around lines 93 - 95, 当前代码在 onClick 回调中通过
document.getElementById("pdf-upload") 直接触发隐藏的 input,这不是 React 的最佳实践;请在组件顶部用
useRef 创建 pdfInputRef 和 coverInputRef(类型为 HTMLInputElement | null),把对应的 input
元素的 ref 属性设置为这些 ref,并把所有使用 document.getElementById("pdf-upload") / cover 的
onClick 回调改为使用 pdfInputRef.current?.click() /
coverInputRef.current?.click(),以便遵循声明式 React 模式并提升可测试性和可维护性(参考相关 onClick 处理器和隐藏的
input 元素)。

57-64: defaultValues 缺少文件字段的初始值。

pdfFilecoverImage 未在 defaultValues 中定义,可能导致 react-hook-form 在某些情况下出现警告或意外行为。

💡 建议修改
   const form = useForm<z.infer<typeof formSchema>>({
     resolver: zodResolver(formSchema),
     defaultValues: {
+      pdfFile: undefined,
+      coverImage: undefined,
       title: "",
       author: "",
       voice: "",
     },
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/UploadForm.tsx` around lines 57 - 64, The form's defaultValues
object is missing initial entries for the file fields pdfFile and coverImage
which can trigger react-hook-form warnings; update the useForm call (form =
useForm<z.infer<typeof formSchema>>) to include pdfFile and coverImage in
defaultValues (e.g., null or an appropriate empty value that matches formSchema)
so the default shape matches the schema and avoids runtime warnings.

44-50: 文件验证使用 z.any() 缺乏类型安全。

当前使用 z.any().refine() 验证文件字段,无法提供类型安全。在 Zod 4 中,建议使用 z.instanceof(File) 或创建自定义文件 schema 以获得更好的类型推断。

♻️ 建议改进
 const formSchema = z.object({
-  pdfFile: z.any().refine((file) => file, "请选择一本 PDF 文件"),
-  coverImage: z.any().optional(),
+  pdfFile: z
+    .instanceof(File, { message: "请选择一本 PDF 文件" })
+    .refine((file) => file.type === "application/pdf", "文件必须是 PDF 格式")
+    .refine((file) => file.size <= 50 * 1024 * 1024, "文件大小不能超过 50MB"),
+  coverImage: z.instanceof(File).optional(),
   title: z.string().min(1, "标题是必填项"),
   author: z.string().min(1, "作者是必填项"),
   voice: z.string().min(1, "请选择一个语音"),
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/UploadForm.tsx` around lines 44 - 50, The formSchema currently
uses z.any().refine for pdfFile and coverImage which lacks type safety; update
the schema to use z.instanceof(File) (or a small custom file schema) so
TypeScript knows these are File objects — e.g., replace pdfFile:
z.any().refine(...) with pdfFile: z.instanceof(File).refine(...)/or a
z.preprocess wrapper to accept FileList and coerce to a File, and make
coverImage optional with z.instanceof(File).optional(); update any code that
reads pdfFile/coverImage to expect File (or File | undefined) to keep types
consistent.
app/layout.tsx (1)

33-35: lang 属性与内容语言不一致。

页面 lang="en" 但 metadata 的 description 使用中文。如果应用主要面向中文用户,建议将 lang 设置为 "zh-CN"

💡 建议修改
     <html
-      lang="en"
+      lang="zh-CN"
       className={`${ibmPlexSerif.variable} ${monaSans.variable} h-full antialiased`}
     >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/layout.tsx` around lines 33 - 35, The HTML root element currently sets
lang="en" in the <html> element (see the html tag in app/layout.tsx) while the
page metadata description is in Chinese; update the lang attribute to match the
content language (e.g., change lang="en" to lang="zh-CN") so the html tag and
your metadata/description are consistent, and review any other metadata
variables (description/metadata) to ensure they all reflect the same language
setting.
type.d.ts (1)

12-27: IBook 接口重新定义了 _id 字段。

Document 接口已包含 _id 属性(类型为 ObjectId)。这里重新定义为 string 类型可能导致类型冲突。如果需要将 _id 作为字符串使用,建议使用 Mongoose 的 lean 查询或在使用时进行转换。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@type.d.ts` around lines 12 - 27, The IBook interface is shadowing Document's
_id (ObjectId) by redefining it as string; remove or correct that redefinition:
either delete the "_id: string" line from IBook so it inherits Document['_id'],
or import and use the correct type (e.g., ObjectId from mongoose) and set "_id:
ObjectId" (or use "_id: Document['_id']") if you must keep the field;
alternatively, keep IBook for lean results but document/convert to string at
usage sites (use .lean() or toString() where needed).
app/globals.css (1)

1105-1124: 注释格式不符合 Stylelint 规范。

被注释的代码块使用了 /*!**!*/ 等非标准注释格式,触发了多个 comment-whitespace-inside 错误。如果这些代码不再需要,建议直接删除;如果需要保留以供参考,请修正注释格式。

💡 建议:删除或修正注释代码

如果保留注释,修正格式:

-/*!* Match the app's existing design system *!*/
-/*.clerk-pricing-container :deep(.cl-pricing-table) {*/
-/*  font-family: var(--font-mona-sans), sans-serif;*/
-/*}*/
+/* Match the app's existing design system */
+/* .clerk-pricing-container :deep(.cl-pricing-table) {
+     font-family: var(--font-mona-sans), sans-serif;
+   } */

或者直接删除这些未使用的注释代码。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 1105 - 1124, The commented CSS block uses
nonstandard delimiters like "/*!*" and "*!*/" which triggers Stylelint
comment-whitespace-inside errors; either remove the entire commented block or
replace the nonstandard delimiters with valid CSS comments (use /* ... */) for
the blocks referencing .clerk-pricing-container and its :deep selectors (e.g.,
:deep(.cl-pricing-table), :deep(.cl-pricing-table-header),
:deep(.cl-pricing-plan-card) and the hover rule), and ensure there are no nested
or malformed comment markers so linting passes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/globals.css`:
- Around line 1080-1094: Replace the hardcoded pseudo-element text (content:
'Your Current Plan') on the .cl-pricingTableCard:first-child
.cl-pricingTableCardContent::after rule with a dynamic value sourced from the
DOM or CSS variable: change the rule to use content: attr(data-current-plan) (or
content: var(--current-plan-text)) and ensure the element matching
.cl-pricingTableCardContent has its data-current-plan attribute (or
--current-plan-text CSS variable) set by your localization code at render time
or via a small client-side script that injects the translated string based on
the app locale; keep the rest of the styling intact.

In `@app/layout.tsx`:
- Around line 38-41: CLERK_AUTH_APPEARANCE_OVERRIDE isn’t being applied because
the value is not passed to ClerkProvider; import the constant
CLERK_AUTH_APPEARANCE_OVERRIDE (from where it’s defined) and supply it as the
appearance prop on ClerkProvider (e.g., ClerkProvider
appearance={CLERK_AUTH_APPEARANCE_OVERRIDE}) so the custom Clerk UI styles are
actually used; update app/layout.tsx to import the symbol and pass it into the
ClerkProvider component.

In `@components/BookCard.tsx`:
- Around line 25-28: The figcaption element is currently outside the figure in
the BookCard component which breaks semantic structure; edit the BookCard JSX so
the <figcaption className="book-card-meta"> (containing the book-card-title and
book-card-author nodes) is moved inside the surrounding <figure> element
(directly after the image/content) so figcaption is a direct child of figure,
preserving accessibility and semantics for the BookCard component.

In `@components/ui/button.tsx`:
- Line 12: The default variant's class string contains an erroneous attribute
selector "[a]:hover:bg-primary/80"; update the class for the default variant
used in the Button component (the mapping key "default" or the variable that
returns that class string) to use the standard Tailwind hover modifier
"hover:bg-primary/80" instead of "[a]:hover:bg-primary/80" so the background
changes on hover as intended.
- Line 3: 在 components/ui/button.tsx 中修复错误的导入:将当前从 "radix-ui" 导入的 Slot 更改为从
"@radix-ui/react-slot" 导入(即把 the Slot import 指向
`@radix-ui/react-slot`),以与项目中其他文件(例如 form.tsx)保持一致并确保 Slot 组件可用。

In `@components/ui/form.tsx`:
- Around line 66-87: The null-check for fieldContext is in the wrong place: move
the verification of fieldContext (and optionally itemContext) to before any
access so you don't call getFieldState or read fieldContext.name when
fieldContext may be absent; in useFormField ensure you first assert fieldContext
exists (throwing the existing error message) then call const fieldState =
getFieldState(fieldContext.name, formState) and only afterwards read
itemContext.id and build the returned ids.

In `@lib/constants.ts`:
- Line 96: The file currently uses a non-null assertion for ASSISTANT_ID (export
const ASSISTANT_ID = process.env.NEXT_PUBLIC_ASSISTANT_ID!) which hides missing
environment variables until runtime; replace the assertion with an explicit
runtime check during module initialization that throws a clear, descriptive
Error if process.env.NEXT_PUBLIC_ASSISTANT_ID is undefined or empty, then export
the validated value (or provide a getter) so callers use the guaranteed
non-empty ASSISTANT_ID; update any references that import ASSISTANT_ID if you
change to a function name to match (e.g., getAssistantId) and ensure the thrown
error message clearly names NEXT_PUBLIC_ASSISTANT_ID.
- Around line 157-167: Several class strings in lib/constants.ts (e.g.,
headerTitle, headerSubtitle, socialButtonsBlockButton,
socialButtonsBlockButtonText, formButtonPrimary, formFieldInput, formFieldLabel,
footerActionLink) use the deprecated Tailwind v4 prefix "!" notation (e.g.,
"!text-2xl"); update each string by moving the "!" to the end of the utility
token (e.g., "text-2xl!") for every utility in the value while preserving
spacing and order and leaving non-prefixed utilities unchanged; scan all
constant values for occurrences of leading "!" and convert them to trailing "!"
consistently.

In `@type.d.ts`:
- Line 119: The import statement "import {PLANS, PlanType} from
'@/lib/subscription-constants';" is placed mid-file; move that import to the top
of type.d.ts with the other imports and remove the unused symbol PLANS (keep
only PlanType) so all imports are consolidated at file start and unused imports
eliminated.
- Line 6: The import of UploadSchema in type.d.ts is invalid because module
'@/lib/zod' doesn't exist; fix by either (A) creating lib/zod.ts that exports
UploadSchema (exporting a Zod schema named UploadSchema matching the expected
fields used by BookUploadFormValues) so the existing import in type.d.ts works,
or (B) remove the import from type.d.ts and replace the dependent type
(BookUploadFormValues) with an explicit interface/type definition inside
type.d.ts (defining pdfFile, optional coverImage, title, author, voice) so no
external schema is required; update references to UploadSchema or
BookUploadFormValues accordingly.

---

Nitpick comments:
In `@app/globals.css`:
- Around line 1105-1124: The commented CSS block uses nonstandard delimiters
like "/*!*" and "*!*/" which triggers Stylelint comment-whitespace-inside
errors; either remove the entire commented block or replace the nonstandard
delimiters with valid CSS comments (use /* ... */) for the blocks referencing
.clerk-pricing-container and its :deep selectors (e.g.,
:deep(.cl-pricing-table), :deep(.cl-pricing-table-header),
:deep(.cl-pricing-plan-card) and the hover rule), and ensure there are no nested
or malformed comment markers so linting passes.

In `@app/layout.tsx`:
- Around line 33-35: The HTML root element currently sets lang="en" in the
<html> element (see the html tag in app/layout.tsx) while the page metadata
description is in Chinese; update the lang attribute to match the content
language (e.g., change lang="en" to lang="zh-CN") so the html tag and your
metadata/description are consistent, and review any other metadata variables
(description/metadata) to ensure they all reflect the same language setting.

In `@components/HeroSection.tsx`:
- Around line 45-67: The repeated step-card JSX in HeroSection.tsx should be
data-driven: create an array (e.g., const steps = [{id: 1, title: '上传 PDF',
desc: '添加你的书籍文件'}, ...]) and replace the three repeated blocks with a single map
over that array inside the HeroSection render/return, rendering the same
structure (library-step-item, library-step-number, library-step-title,
library-step-description) and using step.id as the React key; this centralizes
content so updating titles/descriptions or adding/removing steps only requires
editing the steps array.

In `@components/ui/form.tsx`:
- Around line 27-29: FormFieldContext and FormItemContext are created with
unsafe "{} as ..." defaults which can mask missing providers; change their
default values to null and update useFormField to check for null and throw a
clear error if the hook is used outside the provider (reference
FormFieldContext, FormItemContext, and useFormField). Ensure the context types
allow null (e.g., FormFieldContextValue | null) and replace any direct property
access with a guard that throws a descriptive error so consumers get an explicit
runtime message when the provider is missing.

In `@components/UploadForm.tsx`:
- Around line 93-95: 当前代码在 onClick 回调中通过 document.getElementById("pdf-upload")
直接触发隐藏的 input,这不是 React 的最佳实践;请在组件顶部用 useRef 创建 pdfInputRef 和 coverInputRef(类型为
HTMLInputElement | null),把对应的 input 元素的 ref 属性设置为这些 ref,并把所有使用
document.getElementById("pdf-upload") / cover 的 onClick 回调改为使用
pdfInputRef.current?.click() / coverInputRef.current?.click(),以便遵循声明式 React
模式并提升可测试性和可维护性(参考相关 onClick 处理器和隐藏的 input 元素)。
- Around line 57-64: The form's defaultValues object is missing initial entries
for the file fields pdfFile and coverImage which can trigger react-hook-form
warnings; update the useForm call (form = useForm<z.infer<typeof formSchema>>)
to include pdfFile and coverImage in defaultValues (e.g., null or an appropriate
empty value that matches formSchema) so the default shape matches the schema and
avoids runtime warnings.
- Around line 44-50: The formSchema currently uses z.any().refine for pdfFile
and coverImage which lacks type safety; update the schema to use
z.instanceof(File) (or a small custom file schema) so TypeScript knows these are
File objects — e.g., replace pdfFile: z.any().refine(...) with pdfFile:
z.instanceof(File).refine(...)/or a z.preprocess wrapper to accept FileList and
coerce to a File, and make coverImage optional with
z.instanceof(File).optional(); update any code that reads pdfFile/coverImage to
expect File (or File | undefined) to keep types consistent.

In `@README.md`:
- Around line 1-36: README 当前仍为模板文案,请在 Line 3 后新增一个“项目简介 /
已实现页面”段落,简要说明项目目的、已实现路由(例如 app/page.tsx 对应的首页和“添加新书”页面的路由如 /books/new 或
app/books/new/page.tsx)、每个页面的主要交互点(首页展示书目、添加新书表单字段与提交流程、表单校验要点),并补充本地预览步骤(运行 npm
run dev / pnpm dev 等以及访问 http://localhost:3000
及新增页面的路径);保持描述简短、可读,方便评审与后续接手人员快速了解交付范围。

In `@type.d.ts`:
- Around line 12-27: The IBook interface is shadowing Document's _id (ObjectId)
by redefining it as string; remove or correct that redefinition: either delete
the "_id: string" line from IBook so it inherits Document['_id'], or import and
use the correct type (e.g., ObjectId from mongoose) and set "_id: ObjectId" (or
use "_id: Document['_id']") if you must keep the field; alternatively, keep
IBook for lean results but document/convert to string at usage sites (use
.lean() or toString() where needed).
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 553a82ba-8dc9-4471-8003-d84fa6648e40

📥 Commits

Reviewing files that changed from the base of the PR and between 663e8b6 and c58ffe0.

⛔ Files ignored due to path filters (16)
  • app/favicon.ico is excluded by !**/*.ico
  • package-lock.json is excluded by !**/package-lock.json
  • public/assets/Gemini_Generated_Image_jlix6fjlix6fjlix (1) 1.png is excluded by !**/*.png
  • public/assets/banner.svg is excluded by !**/*.svg
  • public/assets/book-cover.svg is excluded by !**/*.svg
  • public/assets/book.png is excluded by !**/*.png
  • public/assets/desktop-banner.png is excluded by !**/*.png
  • public/assets/hero-illustration.png is excluded by !**/*.png
  • public/assets/image.png is excluded by !**/*.png
  • public/assets/loader.png is excluded by !**/*.png
  • public/assets/logo-full.svg is excluded by !**/*.svg
  • public/assets/logo.png is excluded by !**/*.png
  • public/assets/mobile-banner.png is excluded by !**/*.png
  • public/assets/open-book.svg is excluded by !**/*.svg
  • public/assets/tab-banner.png is excluded by !**/*.png
  • public/assets/wave.png is excluded by !**/*.png
📒 Files selected for processing (26)
  • .gitignore
  • AGENTS.md
  • CLAUDE.md
  • README.md
  • app/(root)/books/new/page.tsx
  • app/(root)/page.tsx
  • app/globals.css
  • app/layout.tsx
  • components.json
  • components/BookCard.tsx
  • components/HeroSection.tsx
  • components/NavBar.tsx
  • components/UploadForm.tsx
  • components/ui/button.tsx
  • components/ui/form.tsx
  • components/ui/input.tsx
  • components/ui/label.tsx
  • eslint.config.mjs
  • lib/constants.ts
  • lib/utils.ts
  • next.config.ts
  • package.json
  • postcss.config.mjs
  • proxy.ts
  • tsconfig.json
  • type.d.ts

Comment thread app/globals.css
Comment thread app/layout.tsx Outdated
Comment thread components/BookCard.tsx
Comment thread components/ui/button.tsx
Comment thread components/ui/button.tsx
Comment thread components/ui/form.tsx
Comment thread lib/constants.ts Outdated
Comment thread lib/constants.ts Outdated
Comment thread type.d.ts
import { Control, FieldPath, FieldValues } from 'react-hook-form';
import { LucideIcon } from 'lucide-react';
import z from 'zod';
import { UploadSchema } from '@/lib/zod';

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 检查 lib/zod.ts 或类似文件是否存在并导出 UploadSchema
fd -e ts -e js zod lib/
rg -l "export.*UploadSchema" --type ts

Repository: lijianhua7/bookified

Length of output: 73


🏁 Script executed:

# 列出 lib 目录的所有文件
find lib -type f 2>/dev/null | head -20

# 检查是否存在 zod 相关的文件
find . -name "*zod*" -type f 2>/dev/null

# 搜索 UploadSchema 的定义(不仅仅是导入)
rg "export.*UploadSchema|const UploadSchema" --type ts --type tsx

Repository: lijianhua7/bookified

Length of output: 138


🏁 Script executed:

# 搜索 UploadSchema 的定义位置(任何地方)
rg "UploadSchema" --type ts

# 读取 type.d.ts 查看实际内容
cat -n type.d.ts | head -100

Repository: lijianhua7/bookified

Length of output: 3071


🏁 Script executed:

# 检查 tsconfig 或 path alias 配置
cat tsconfig.json 2>/dev/null | grep -A 10 "paths\|@/"

# 搜索是否有类似的 schema 定义在其他文件中
rg "z\.object|zod" --type ts | head -20

Repository: lijianhua7/bookified

Length of output: 592


🏁 Script executed:

# 检查 UploadForm.tsx 中的 formSchema 定义
rg -A 15 "const formSchema = z.object" components/UploadForm.tsx

Repository: lijianhua7/bookified

Length of output: 659


UploadSchema 导入将导致编译错误。

@/lib/zod 模块不存在(仅在 lib/ 目录中找到 constants.tsutils.ts),而 type.d.ts 第 6 行试图从该模块导入 UploadSchema。同时,第 56 行的类型定义依赖于这个不存在的导入。

解决方案:

选项 1:创建并导出 schema
lib/zod.ts 中定义:

import { z } from "zod";

export const UploadSchema = z.object({
  pdfFile: z.any().refine((file) => file, "请选择一本 PDF 文件"),
  coverImage: z.any().optional(),
  title: z.string().min(1, "标题是必填项"),
  author: z.string().min(1, "作者是必填项"),
  voice: z.string().min(1, "请选择一个语音"),
});

选项 2:直接在 type.d.ts 中定义类型
删除第 6 行的导入,将第 56 行改为:

export interface BookUploadFormValues {
  pdfFile: File;
  coverImage?: File;
  title: string;
  author: string;
  voice: string;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@type.d.ts` at line 6, The import of UploadSchema in type.d.ts is invalid
because module '@/lib/zod' doesn't exist; fix by either (A) creating lib/zod.ts
that exports UploadSchema (exporting a Zod schema named UploadSchema matching
the expected fields used by BookUploadFormValues) so the existing import in
type.d.ts works, or (B) remove the import from type.d.ts and replace the
dependent type (BookUploadFormValues) with an explicit interface/type definition
inside type.d.ts (defining pdfFile, optional coverImage, title, author, voice)
so no external schema is required; update references to UploadSchema or
BookUploadFormValues accordingly.

Comment thread type.d.ts Outdated
@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

An unexpected error occurred while generating fixes: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)

@coderabbitai

coderabbitai Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 5 file(s) based on 7 unresolved review comments.

Files modified:

  • app/globals.css
  • app/layout.tsx
  • components/ui/form.tsx
  • lib/constants.ts
  • type.d.ts

Commit: ec0e4e045e7296bb4630f5dbb730fcd0c0c1b79f

The changes have been pushed to the setup-and-ui branch.

Time taken: 7m 41s

Fixed 5 file(s) based on 7 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

♻️ Duplicate comments (2)
components/ui/form.tsx (1)

27-29: ⚠️ Potential issue | 🟠 Major

上下文默认值写法使防御检查失效,useFormField 在脱离 Provider 时不会按预期报错。

Line 27 和 Line 48 用 {} as ... 作为默认值后,Line 71 的 if (!fieldContext) 永远不会触发;随后 Line 75/Line 77 可能在 name/idundefined 时继续执行,导致状态读取和 a11y id 退化为无效值。这个问题与之前评论是同类根因,当前仍未修复。

🔧 建议修复
-type FormFieldContextValue<
+type FormFieldContextValue<
   TFieldValues extends FieldValues = FieldValues,
   TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
 > = {
   name: TName
 }
 
-const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue
-)
+const FormFieldContext = React.createContext<FormFieldContextValue | undefined>(
+  undefined
+)
@@
 type FormItemContextValue = {
   id: string
 }
 
-const FormItemContext = React.createContext<FormItemContextValue>(
-  {} as FormItemContextValue
-)
+const FormItemContext = React.createContext<FormItemContextValue | undefined>(
+  undefined
+)
@@
 const useFormField = () => {
   const fieldContext = React.useContext(FormFieldContext)
   const itemContext = React.useContext(FormItemContext)
   const { getFieldState, formState } = useFormContext()
 
-  if (!fieldContext) {
+  if (!fieldContext?.name) {
     throw new Error("useFormField should be used within <FormField>")
   }
+  if (!itemContext?.id) {
+    throw new Error("useFormField should be used within <FormItem>")
+  }
 
   const fieldState = getFieldState(fieldContext.name, formState)
-
-  const { id } = itemContext
+  const { id } = itemContext

Also applies to: 48-50, 66-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/form.tsx` around lines 27 - 29, The FormFieldContext default
value uses a casted empty object which disables the null-check in useFormField;
change the context creation (FormFieldContext) to allow undefined as the default
(i.e., React.createContext<FormFieldContextValue | undefined>(undefined)) and
update useFormField to check for undefined and throw a clear error when the hook
is used outside the Provider; ensure functions/props that read name/id
(referenced in useFormField and any callers) account for possibly undefined
context until the explicit error is thrown.
app/layout.tsx (1)

39-39: ⚠️ Potential issue | 🟠 Major

appearance 这里少了一层 elements,而且还缺 Tailwind v4 的 cssLayerName

lib/constants.ts:156-172 当前这个常量还是 element-class map,不是完整的 appearance 对象。直接传给 ClerkProvider 时,这些类不会落到 appearance.elements 上;Clerk 官方文档也是通过 appearance.elements 传元素级类名,并建议在 Tailwind v4 下同时设置 cssLayerName 与对应的 CSS layer 顺序。 (clerk.com)

🔧 建议修复
-        <ClerkProvider appearance={CLERK_AUTH_APPEARANCE_OVERRIDE}>
+        <ClerkProvider
+          appearance={{
+            elements: CLERK_AUTH_APPEARANCE_OVERRIDE,
+            cssLayerName: "clerk",
+          }}
+        >

补充到 app/globals.css 顶部:

-@import 'tailwindcss';
+@layer theme, base, clerk, components, utilities;
+@import 'tailwindcss';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/layout.tsx` at line 39, The current CLERK_AUTH_APPEARANCE_OVERRIDE (an
element-class map from lib/constants) is being passed directly to ClerkProvider;
wrap it under an appearance.elements property and also add a cssLayerName entry
for Tailwind v4 compatibility so ClerkProvider receives a full appearance
object. Update the ClerkProvider usage (symbol: ClerkProvider) in layout.tsx to
pass appearance: { elements: CLERK_AUTH_APPEARANCE_OVERRIDE, cssLayerName:
'<your-tailwind-layer-name>' } and ensure the same layer name and layer ordering
are declared at the top of globals.css as recommended.
🧹 Nitpick comments (2)
type.d.ts (1)

5-5: 建议使用命名导入方式引入 Zod。

Zod 的标准导入方式是命名导入 { z },而非默认导入。虽然某些版本可能支持默认导入,但使用命名导入更符合 Zod 官方推荐的用法。

♻️ 建议修改
-import z from 'zod';
+import { z } from 'zod';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@type.d.ts` at line 5, The import uses a default import "import z from 'zod';"
which is non-idiomatic; replace it with a named import so the module uses Zod's
recommended API (change the import to use the named symbol z, i.e. import { z }
from 'zod') and update any references that currently expect the default import
to use the named "z" symbol instead.
app/globals.css (1)

1-7: 不要为了过 lint 改掉合法的 Tailwind v4 指令。

@theme@custom-variant 都是 Tailwind v4 官方支持的语法;当前静态检查把它们报成 unknown at-rule,更像是 Stylelint 配置没跟上,而不是这里的 CSS 本身有问题。建议在 lint 配置里放行这些 at-rule,否则 globals.css 后续每次改动都会被误报。 (tailwindcss.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 1 - 7, The linter is flagging valid Tailwind v4
at-rules (`@theme` and `@custom-variant`) as unknown; update the Stylelint/linters
config to whitelist these Tailwind at-rules so globals.css stops producing false
positives. Modify your linter settings (e.g., the at-rule-no-unknown rule or
equivalent) to add "theme" and "custom-variant" to ignoreAtRules (and include
other Tailwind v4 rules if missing), then re-run lint to verify `@theme` and
`@custom-variant` usages are accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/globals.css`:
- Around line 1105-1124: The comment block starting with the invalid token
"/*!*" and ending with "*!*/" (around the commented rules for
.clerk-pricing-container :deep(.cl-pricing-table), .cl-pricing-table-header, and
.cl-pricing-plan-card) is not valid CSS and triggers Stylelint; replace the
nonstandard wrappers with a normal CSS comment delimiter (/* ... */) or delete
the dead code entirely, and ensure no remaining occurrences of "/*!*" or "*!*/"
exist in globals.css so CI/stylelint stops erroring.
- Around line 129-164: 当前 .dark 主题只定义了部分 token(如
--background/--foreground),但页面上还有许多样式直接使用自定义 token(例如
--text-primary、--text-secondary、--bg-secondary、--accent-light、--border-subtle
等),导致暗色模式下出现错误配色;请在 .dark 选择器内补齐这些缺失的 design token,将所有在 globals.css
中被引用的自定义属性(至少包括
--text-primary、--text-secondary、--bg-primary/--bg-secondary、--accent-light、--accent、--border-subtle、--text-muted
以及 sidebar/tooltip/popover 相关 token)覆盖为适合暗色背景的值,保证与现有 --background/--foreground
一致并替换掉任何仍指向浅色主题的变量;在修改时以现有浅色 token 命名为参考并保持命名一致,以便其他样式不需改动(定位点:.dark
选择器和在文件中查找使用这些 token 的位置如第 186、251、652 附近)。
- Around line 1062-1094: The CSS incorrectly uses :first-child to mark the
active plan which can target the wrong card; instead add an explicit data
attribute on the real active card (e.g. data-active="true") and update selectors
to target .cl-pricingTableCard[data-active="true"] for the border and
.cl-pricingTableCard[data-active="true"] .cl-pricingTableCardContent for the
badge/indicator; also place the localized text attribute (data-current-plan) on
the .cl-pricingTableCardContent element (not the parent) because attr() on the
pseudo-element can only read the host element’s own attributes. Ensure you
replace any .cl-pricingTableCard:first-child and
.cl-pricingTableCard:first-child .cl-pricingTableCardContent::after usages with
the new attribute-based selectors and keep the existing styling values.
- Around line 1008-1060: The rule that targets all buttons inside
.clerk-pricing-table-wrapper is too broad and conflicts with segmented/tab rules
(.cl-segmentedControlButton, .cl-tabButton, [role='tablist'] button); restrict
the dark-CTA styles by replacing the broad selector
(.clerk-pricing-table-wrapper button) with a more specific selector that only
matches real primary actions (e.g. .clerk-pricing-table-wrapper .cta,
.clerk-pricing-table-wrapper button[data-variant='primary'] or
.clerk-pricing-table-wrapper button[data-role='cta']), and update the matching
hover and disabled rules (.clerk-pricing-table-wrapper button:hover and the
disabled selectors) to use the same specific selector so segmented controls and
tab buttons inside the pricing wrapper keep their light-text rules. Ensure you
also update any markup or docs to add the chosen .cta / data-attribute to the
real CTA buttons if not already present.

In `@app/layout.tsx`:
- Around line 34-36: The root html lang attribute is hardcoded to "en" while
your metadata and NavBar (components/NavBar.tsx:8-15) are Chinese; update the
html element in app/layout.tsx (the <html ...
className={`${ibmPlexSerif.variable} ${monaSans.variable} h-full antialiased`}>
tag) to use "zh-CN" instead of "en" now, and add a TODO or prepare to inject
locale dynamically later when you wire up runtime/localization logic.

---

Duplicate comments:
In `@app/layout.tsx`:
- Line 39: The current CLERK_AUTH_APPEARANCE_OVERRIDE (an element-class map from
lib/constants) is being passed directly to ClerkProvider; wrap it under an
appearance.elements property and also add a cssLayerName entry for Tailwind v4
compatibility so ClerkProvider receives a full appearance object. Update the
ClerkProvider usage (symbol: ClerkProvider) in layout.tsx to pass appearance: {
elements: CLERK_AUTH_APPEARANCE_OVERRIDE, cssLayerName:
'<your-tailwind-layer-name>' } and ensure the same layer name and layer ordering
are declared at the top of globals.css as recommended.

In `@components/ui/form.tsx`:
- Around line 27-29: The FormFieldContext default value uses a casted empty
object which disables the null-check in useFormField; change the context
creation (FormFieldContext) to allow undefined as the default (i.e.,
React.createContext<FormFieldContextValue | undefined>(undefined)) and update
useFormField to check for undefined and throw a clear error when the hook is
used outside the Provider; ensure functions/props that read name/id (referenced
in useFormField and any callers) account for possibly undefined context until
the explicit error is thrown.

---

Nitpick comments:
In `@app/globals.css`:
- Around line 1-7: The linter is flagging valid Tailwind v4 at-rules (`@theme` and
`@custom-variant`) as unknown; update the Stylelint/linters config to whitelist
these Tailwind at-rules so globals.css stops producing false positives. Modify
your linter settings (e.g., the at-rule-no-unknown rule or equivalent) to add
"theme" and "custom-variant" to ignoreAtRules (and include other Tailwind v4
rules if missing), then re-run lint to verify `@theme` and `@custom-variant` usages
are accepted.

In `@type.d.ts`:
- Line 5: The import uses a default import "import z from 'zod';" which is
non-idiomatic; replace it with a named import so the module uses Zod's
recommended API (change the import to use the named symbol z, i.e. import { z }
from 'zod') and update any references that currently expect the default import
to use the named "z" symbol instead.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7de14d13-a00b-4e7d-9bd5-3a6fd89f48bb

📥 Commits

Reviewing files that changed from the base of the PR and between c58ffe0 and ec0e4e0.

📒 Files selected for processing (5)
  • app/globals.css
  • app/layout.tsx
  • components/ui/form.tsx
  • lib/constants.ts
  • type.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/constants.ts

Comment thread app/globals.css
Comment thread app/globals.css
Comment thread app/globals.css Outdated
Comment thread app/layout.tsx
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 2 file(s) based on 5 unresolved review comments.

Files modified:

  • app/globals.css
  • app/layout.tsx

Commit: c27dc9cd7d219d6c3ca0ac0f320df48490d047f0

The changes have been pushed to the setup-and-ui branch.

Time taken: 6m 41s

Fixed 2 file(s) based on 5 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
app/globals.css (1)

1044-1076: 可选优化:使用复杂 :not() 语法简化选择器。

当前使用链式 :not() 伪类排除非 CTA 按钮,功能正确但略显冗长。Stylelint 建议使用 CSS Selectors Level 4 的复杂 :not() 语法:

♻️ 可选重构
-/* CTA buttons only - exclude segmented controls and tabs */
-.clerk-pricing-table-wrapper button:not(.cl-segmentedControlButton):not(.cl-tabButton):not([role='tablist'] button) {
+/* CTA buttons only - exclude segmented controls and tabs */
+.clerk-pricing-table-wrapper button:not(.cl-segmentedControlButton, .cl-tabButton, [role='tablist'] button) {

同样适用于 :hover:disabled 状态选择器(Lines 1059, 1066-1068)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 1044 - 1076, The selector chains in the
.clerk-pricing-table-wrapper rules use repeated :not() pseudo-classes and are
verbose; replace the chained
:not(.cl-segmentedControlButton):not(.cl-tabButton):not([role='tablist'] button)
with a single complex :not(...) grouping (CSS Selectors Level 4) to exclude all
non-CTA buttons in the main rule, and apply the same refactor to the
corresponding :hover and disabled/aria-disabled selectors so all three rules
(.clerk-pricing-table-wrapper button, its :hover, and its :disabled variants)
use the single consolidated :not(...) expression referencing
.cl-segmentedControlButton, .cl-tabButton, and [role='tablist'] button to
simplify and satisfy stylelint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/globals.css`:
- Around line 1078-1109: The CSS relies on attributes data-active and
data-current-plan but nothing sets them; update the pricing card rendering logic
(e.g., the PricingCard or PricingTable component that outputs elements with
class .cl-pricingTableCard and .cl-pricingTableCardContent) to: set
data-active="true" on the active card element (use your existing active plan
state/prop) and set data-current-plan on the .cl-pricingTableCardContent element
to the localized "Current Plan" string (or empty string when not active) at
render time so the CSS selectors .cl-pricingTableCard[data-active="true"] and
.cl-pricingTableCardContent::after with attr(data-current-plan) can take effect.
- Around line 1120-1139: Remove the commented-out legacy CSS block that causes
Stylelint comment-whitespace-inside errors and contains invalid Vue :deep()
selectors for this app; specifically delete the entire commented section
referencing .clerk-pricing-container and its nested commented selectors
(:deep(.cl-pricing-table), :deep(.cl-pricing-table-header),
:deep(.cl-pricing-plan-card), and :deep(.cl-pricing-plan-card:hover)) so only
valid, active CSS remains.

---

Nitpick comments:
In `@app/globals.css`:
- Around line 1044-1076: The selector chains in the .clerk-pricing-table-wrapper
rules use repeated :not() pseudo-classes and are verbose; replace the chained
:not(.cl-segmentedControlButton):not(.cl-tabButton):not([role='tablist'] button)
with a single complex :not(...) grouping (CSS Selectors Level 4) to exclude all
non-CTA buttons in the main rule, and apply the same refactor to the
corresponding :hover and disabled/aria-disabled selectors so all three rules
(.clerk-pricing-table-wrapper button, its :hover, and its :disabled variants)
use the single consolidated :not(...) expression referencing
.cl-segmentedControlButton, .cl-tabButton, and [role='tablist'] button to
simplify and satisfy stylelint.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d0b344f7-07c0-481c-bff9-22d122bb0fd1

📥 Commits

Reviewing files that changed from the base of the PR and between ec0e4e0 and c27dc9c.

📒 Files selected for processing (2)
  • app/globals.css
  • app/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/layout.tsx

Comment thread app/globals.css
Comment on lines +1078 to +1109
/* Add accent border to active plan card
Note: Use data-active="true" attribute to explicitly mark the active card. */
.cl-pricingTableCard[data-active="true"] {
border: 2px solid #663820 !important;
}

/* Style the Active badge with better contrast */
.cl-badge,
.cl-pricingTableCard [class*='badge'],
.cl-pricingTableCard span[class*='Badge'] {
background-color: #663820 !important;
color: white !important;
font-weight: 600 !important;
padding: 0.25rem 0.75rem !important;
border-radius: 9999px !important;
}

/* Add "Current Plan" indicator to active plan that doesn't have a button
Note: Uses attr(data-current-plan) for localization support.
Set the data-current-plan attribute on .cl-pricingTableCardContent at render time. */
.cl-pricingTableCard[data-active="true"] .cl-pricingTableCardContent::after {
content: attr(data-current-plan);
display: block;
text-align: center;
padding: 0.75rem 1.5rem;
margin-top: 1rem;
background-color: #f3e4c7 !important;
color: #212a3b !important;
font-weight: 600;
border-radius: 0.75rem;
font-size: 1.125rem;
}

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 搜索 data-active 和 data-current-plan 属性的设置位置
echo "=== 查找 data-active 属性 ==="
rg -n "data-active" --type=ts --type=tsx --type=js --type=jsx -g '!*.css'

echo ""
echo "=== 查找 data-current-plan 属性 ==="
rg -n "data-current-plan" --type=ts --type=tsx --type=js --type=jsx -g '!*.css'

echo ""
echo "=== 查找 PricingTable 组件使用 ==="
rg -n "PricingTable|pricing.table" -i --type=ts --type=tsx --type=js --type=jsx

echo ""
echo "=== 查找订阅页面 ==="
fd -i "subscription" -e tsx -e ts -e jsx -e js

Repository: lijianhua7/bookified

Length of output: 269


🏁 Script executed:

#!/bin/bash
# Try broader search using different approach
echo "=== 查找所有文件中的 data-active ==="
rg -n "data-active" --type=css --type=html

echo ""
echo "=== 直接搜索 .tsx 和 .jsx 文件 ==="
find . -type f \( -name "*.tsx" -o -name "*.jsx" \) | head -20

echo ""
echo "=== 在所有文件中搜索 data-active (不限制类型) ==="
rg -n "data-active" --type-list | grep -i type
rg -n "data-active" -l

echo ""
echo "=== 搜索任何包含 'active' 的属性设置 ==="
rg -n "setAttribute.*active|data-active" --no-type-list

Repository: lijianhua7/bookified

Length of output: 1604


🏁 Script executed:

#!/bin/bash
# 确认 data-current-plan 属性完全未被使用
echo "=== 查找 data-current-plan ==="
rg -n "data-current-plan"

echo ""
echo "=== 查找所有包含 'current' 和 'plan' 的代码 ==="
rg -n "current.*plan|plan.*current" -i

echo ""
echo "=== 查找所有 setAttribute 或动态属性设置 ==="
rg -n "setAttribute|\.dataset\.|data-" | head -30

Repository: lijianhua7/bookified

Length of output: 3157


CSS 样式依赖的 data-activedata-current-plan 属性从未在代码中被设置。

虽然 CSS 已正确从 :first-child 和硬编码文本改为使用 [data-active="true"]attr(data-current-plan),但整个仓库中完全没有代码设置这些必需的数据属性。CSS 注释明确指出这些属性应在"渲染时"被设置,但此类实现代码不存在。结果是这些 CSS 规则成为无法生效的死代码。需要补充相应的组件逻辑来设置这些属性,否则这些样式将永远不会被应用。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 1078 - 1109, The CSS relies on attributes
data-active and data-current-plan but nothing sets them; update the pricing card
rendering logic (e.g., the PricingCard or PricingTable component that outputs
elements with class .cl-pricingTableCard and .cl-pricingTableCardContent) to:
set data-active="true" on the active card element (use your existing active plan
state/prop) and set data-current-plan on the .cl-pricingTableCardContent element
to the localized "Current Plan" string (or empty string when not active) at
render time so the CSS selectors .cl-pricingTableCard[data-active="true"] and
.cl-pricingTableCardContent::after with attr(data-current-plan) can take effect.

Comment thread app/globals.css Outdated
@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 2 file(s) based on 2 unresolved review comments.

Files modified:

  • app/globals.css
  • package.json

Commit: 02410251df96e811c8e0cca7988d8dae5c46ce04

The changes have been pushed to the setup-and-ui branch.

Time taken: 7m 19s

Fixed 2 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (3)
app/globals.css (3)

245-248: 考虑使用 CSS 变量统一字体声明。

文件中多处直接使用 font-family: 'IBM Plex Serif', serif(如 .form-btn.btn-primary.vapi-badge-ai-text 等)。项目已在 @theme inline 中定义了 --font-serif: var(--font-ibm-plex-serif),建议统一使用该变量以提高可维护性。

♻️ 示例
 .form-btn {
   `@apply` !w-full h-14 rounded-[10px] bg-[`#663820`] cursor-pointer hover:bg-[`#7a4528`] text-xl text-white font-bold transition-colors;
-  font-family: 'IBM Plex Serif', serif;
+  font-family: var(--font-serif);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 245 - 248, 在多个规则(例如
.form-btn、.btn-primary、.vapi-badge-ai-text)中用到固定字体声明,请把硬编码的 "font-family: 'IBM
Plex Serif', serif" 替换为项目已定义的 CSS 变量(--font-serif 或
var(--font-serif));定位到这些选择器并修改它们的 font-family 属性以引用该变量,确保统一使用 --font-serif
来提高可维护性和主题一致性。

1044-1076: 建议简化 :not() 伪类选择器。

Stylelint 提示使用复杂 :not() 语法。现代 CSS(Selectors Level 4)支持在 :not() 中使用选择器列表,可以简化这些选择器。

♻️ 建议简化
-/* CTA buttons only - exclude segmented controls and tabs */
-.clerk-pricing-table-wrapper button:not(.cl-segmentedControlButton):not(.cl-tabButton):not([role='tablist'] button) {
+/* CTA buttons only - exclude segmented controls and tabs */
+.clerk-pricing-table-wrapper button:not(.cl-segmentedControlButton, .cl-tabButton, [role='tablist'] button) {
   height: 3rem !important;
   /* ... */
 }

-.clerk-pricing-table-wrapper button:not(.cl-segmentedControlButton):not(.cl-tabButton):not([role='tablist'] button):hover {
+.clerk-pricing-table-wrapper button:not(.cl-segmentedControlButton, .cl-tabButton, [role='tablist'] button):hover {
   /* ... */
 }

:disabled[disabled][aria-disabled='true'] 的选择器也可类似简化。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 1044 - 1076, 简化冗长的 :not() 和 disabled
变体选择器:将出现的多个链式 :not()(例如在 .clerk-pricing-table-wrapper
button:not(.cl-segmentedControlButton):not(.cl-tabButton):not([role='tablist']
button))替换为单个 :not() 接受选择器列表(如 :not(.cl-segmentedControlButton, .cl-tabButton,
[role='tablist'] button)),并把三种禁用状态的重复规则合并成一个更简洁的组合选择器(例如使用同样的单一 :not(...) 并配合
:is(:disabled, [disabled], [aria-disabled="true"]) 或等价的选择器列表),保持样式行为不变但满足
Stylelint 的建议。

891-903: Clerk 样式存在双重定义。

这里的 CSS 规则与 lib/constants.ts 中的 CLERK_AUTH_APPEARANCE_OVERRIDE 配置存在重复。两者都对 .cl-formButtonPrimary 设置了相同的颜色值(#212a3b#3d485e)和圆角。虽然 !important 确保 CSS 优先,但双重维护增加了不一致风险。

建议选择其一作为单一样式来源:要么仅使用 CSS 覆盖(删除 constants 中的相关配置),要么仅使用 Clerk 的 appearance API(删除这里的 CSS)。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/globals.css` around lines 891 - 903, There is a duplicate styling source
for the Clerk button: the .cl-formButtonPrimary rules in app/globals.css
conflict with the CLERK_AUTH_APPEARANCE_OVERRIDE object in lib/constants.ts;
pick one source of truth and remove the other to avoid double maintenance and
potential divergence. Either delete or comment out the .cl-formButtonPrimary
rules in app/globals.css (the hover block too) if you want to keep
CLERK_AUTH_APPEARANCE_OVERRIDE as the sole appearance config, or remove the
CLERK_AUTH_APPEARANCE_OVERRIDE entry in lib/constants.ts and rely on the CSS
override in globals.css; update whichever location you keep to ensure the
background colors (`#212a3b`, `#3d485e`), border-radius, and box-shadow values match
exactly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/globals.css`:
- Around line 245-248: 在多个规则(例如
.form-btn、.btn-primary、.vapi-badge-ai-text)中用到固定字体声明,请把硬编码的 "font-family: 'IBM
Plex Serif', serif" 替换为项目已定义的 CSS 变量(--font-serif 或
var(--font-serif));定位到这些选择器并修改它们的 font-family 属性以引用该变量,确保统一使用 --font-serif
来提高可维护性和主题一致性。
- Around line 1044-1076: 简化冗长的 :not() 和 disabled 变体选择器:将出现的多个链式 :not()(例如在
.clerk-pricing-table-wrapper
button:not(.cl-segmentedControlButton):not(.cl-tabButton):not([role='tablist']
button))替换为单个 :not() 接受选择器列表(如 :not(.cl-segmentedControlButton, .cl-tabButton,
[role='tablist'] button)),并把三种禁用状态的重复规则合并成一个更简洁的组合选择器(例如使用同样的单一 :not(...) 并配合
:is(:disabled, [disabled], [aria-disabled="true"]) 或等价的选择器列表),保持样式行为不变但满足
Stylelint 的建议。
- Around line 891-903: There is a duplicate styling source for the Clerk button:
the .cl-formButtonPrimary rules in app/globals.css conflict with the
CLERK_AUTH_APPEARANCE_OVERRIDE object in lib/constants.ts; pick one source of
truth and remove the other to avoid double maintenance and potential divergence.
Either delete or comment out the .cl-formButtonPrimary rules in app/globals.css
(the hover block too) if you want to keep CLERK_AUTH_APPEARANCE_OVERRIDE as the
sole appearance config, or remove the CLERK_AUTH_APPEARANCE_OVERRIDE entry in
lib/constants.ts and rely on the CSS override in globals.css; update whichever
location you keep to ensure the background colors (`#212a3b`, `#3d485e`),
border-radius, and box-shadow values match exactly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9141dace-d7ba-44bd-89f9-5b54e9df79ae

📥 Commits

Reviewing files that changed from the base of the PR and between c27dc9c and 0241025.

📒 Files selected for processing (2)
  • app/globals.css
  • package.json
✅ Files skipped from review due to trivial changes (1)
  • package.json

@lijianhua7 lijianhua7 merged commit 07d29d3 into main Apr 1, 2026
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Apr 20, 2026
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.

1 participant