# Universal Instructions for React / Next.js Projects
> Purpose: General rules for developing various projects with React + TypeScript, Next.js + TypeScript, and Tailwind CSS.
> Usage: Place this file in the root of a new project as `AGENTS.md`, `CLAUDE.md`, or `PROJECT_RULES.md`, or use it as a base instruction set for an AI agent.
> Important: These instructions do not contain product-specific rules. Keep everything related to an individual project in a separate `PROJECT_RULES.md` file.
---
# 1. Core Principle
Build a production-ready application, not a collection of disconnected components.
Always follow this sequence:
1. Review the current project structure, `package.json`, routing, UI primitives, stores, hooks, schemas, and project rules.
2. Find existing actions, helpers, schemas, and components that can be reused.
3. Identify the smallest change required for the task.
4. Preserve existing behavior.
5. Implement each new feature end to end: model, validation, UI, storage/import/export, edge cases, and verification.
6. Run the relevant checks and report the results honestly.
Do not add dependencies, abstractions, a global store, or an architectural layer unless they are genuinely necessary.
Use `shadcn/ui` by default for UI work. Do not add another UI kit on top of it without a clear reason.
---
# 2. Choosing Between React and Next.js
Use Next.js when the project needs:
- routing;
- SEO;
- SSR / Server Components;
- Server Actions;
- Route Handlers / API routes;
- authentication;
- database access;
- private environment variables;
- content publishing.
Use React + Vite when:
- the application is entirely client-side;
- SEO is not required;
- it is a local tool, dashboard, editor, admin panel, or desktop-like UI;
- the server already exists as a separate service.
Do not choose Next.js simply because it is popular. Do not add Redux, Zustand, React Query, a form library, or another UI kit without a specific reason.
---
# 3. Default Stack and Checks
Use the following by default:
- React;
- TypeScript in strict mode;
- Tailwind CSS;
- `shadcn/ui` as the required UI approach for clean design and rapid interface development;
- Lucide React or the icon library used by the current shadcn configuration;
- ESLint;
- a shared `cn()` helper;
- runtime validation for external data;
- accessible HTML elements.
Use `shadcn/ui` as the primary source of UI primitives: buttons, inputs, selects, dialogs, sheets, dropdowns, tooltips, tabs, carousels, cards, badges, skeletons, scroll areas, and other required components. Create custom primitives only when shadcn does not provide a suitable component or when the project already has a stable local primitive.
For an MVP, begin with mock/JSON/localStorage data and validate local user flows first. Add the backend, database, payments, authentication, and external integrations last, once the UI, models, and flows are clear.
At a minimum, run these commands after code changes:
```bash
npm run typecheck
npm run lint
npm run build
```
Do not claim that the project works if these commands were not run or completed with errors.
---
# 4. Architecture
For Next.js projects expected to grow, keep source code inside `src/` by default: `src/app`, `src/components`, `src/lib`, `src/data`, `src/hooks`, and `src/features`. Keep root-level support folders and files (`public`, configuration files, lockfiles, and README) in the project root.
For small projects, the following structure is acceptable:
```text
src/
app/ or pages/
components/
features/
lib/
shared/
```
For medium and large projects, use an FSD-like approach:
```text
src/
app/ # bootstrap, providers, layouts, routes
views/ # page-level composition
widgets/ # large UI blocks
features/ # user workflows
entities/ # domain model
shared/ # generic helpers, config, thin wrappers around shadcn/ui
```
Import direction:
```text
app/views -> widgets -> features -> entities -> shared
```
Do not:
- import `widgets` into `features`;
- place business logic in `shared`;
- turn `shared/lib` into a dumping ground for unrelated functions;
- duplicate mutation logic across multiple UI components;
- use deep imports into another module's internals when that module exposes a public API.
---
# 5. Public API
Every feature, entity, or shared UI folder should expose a clear public API through `index.ts` when the module is used externally. For shadcn primitives, the public API usually already lives in `components/ui/*` or the project's local UI layer.
Good:
```ts
import { createTask } from "@/features/create-task";
```
Bad:
```ts
import { createTask } from "@/features/create-task/model/createTask";
```
Exception: internal code within the same feature or entity.
---
# 6. TypeScript
Required:
- enable `strict: true`;
- do not use `any` except in isolated interoperability code;
- do not hide type errors with `as` assertions;
- use discriminated unions for complex state;
- validate runtime JSON with a schema;
- do not create multiple identical types without a meaningful reason.
Example state type:
```ts
type LoadState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
```
---
# 7. React State and Effects
Store state where it actually belongs:
| State type | Where to store it |
| ------------ | ----------------------------------------------------------- |
| Local UI | `useState`, `useReducer` |
| URL state | route/search parameters |
| Server state | server rendering or a cache/query layer |
| Form state | form hook/library |
| Global UI | a small store when necessary |
| Domain state | entity/store when the state is shared across multiple flows |
Do not put the following in a global store:
- hover state;
- the state of a single dropdown;
- the draft value of a single input;
- the state of a single modal;
- the temporary selected tab of one component.
Use `useEffect` to synchronize with external systems:
- browser APIs;
- timers;
- subscriptions;
- external stores;
- DOM integrations.
Do not use `useEffect` for derived values.
Bad:
```tsx
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
```
Good:
```tsx
const fullName = `${firstName} ${lastName}`;
```
---
# 8. Next.js Boundaries
In the App Router, components are Server Components by default.
Add `"use client"` only where you need:
- event handlers;
- local state;
- effects;
- `window`, `document`, or `localStorage`;
- drag and drop;
- `contenteditable`;
- client-only libraries.
Do not make an entire layout a Client Component without a clear need.
Server-only code includes:
- database access;
- authentication;
- private API clients;
- secret environment variables;
- webhooks;
- access checks.
Never import a server-only module into a Client Component.
---
# 9. Runtime Validation and Migrations
Validate all external data at the boundary:
- request bodies;
- form data;
- URL/search parameters;
- uploaded files;
- imported JSON;
- localStorage/IndexedDB data;
- responses from external APIs.
When adding a new model field, update the entire lifecycle:
1. TypeScript type.
2. Runtime schema.
3. Factory/default values.
4. Parser/migration for legacy data.
5. Normalization helpers.
6. Import/export.
7. Search/filter indexing, if the field should be searchable.
8. Undo/redo snapshots, if users can edit the field.
9. UI for creating, editing, and clearing the field.
10. Edge cases and checks.
Example:
```ts
return {
...item,
status: item.status ?? "active",
tags: normalizeTags(item.tags),
dueDate: normalizeDate(item.dueDate),
};
```
Do not add a model field only in the UI.
---
# 10. Forms
Every form must include:
- a validation schema;
- field errors;
- a submitting/loading state;
- a disabled submit button while submitting;
- protection against duplicate submissions;
- an error state;
- success behavior;
- reset/draft behavior, when applicable.
A form is not complete if it works only when the request succeeds perfectly.
---
# 11. shadcn/ui and Shared UI
Use `shadcn/ui` by default to build clean, consistent interfaces quickly.
Rules:
- first check whether the required component exists in the shadcn registry;
- add shadcn components through the CLI or the project's established local method;
- do not create a custom Button, Input, Modal, Dropdown, Tooltip, Tabs, or Card if shadcn already covers the use case;
- adapt shadcn components through `className`, variants, and composition instead of copying similar components;
- keep business components separate from primitives: `components/marketplace`, `features/*/ui`, `widgets/*`, or `entities/*/ui`;
- keep only shadcn primitives and thin reusable wrappers in `components/ui` or `shared/ui`;
- do not place product-specific business components there;
- if shadcn does not provide a component, create a minimal local wrapper consistent with the current shadcn configuration.
Base set of shadcn components for productivity interfaces:
```text
button
input
select
textarea
checkbox
switch
dialog
sheet
dropdown-menu
popover
tooltip
tabs
card
badge
avatar
separator
scroll-area
skeleton
carousel
accordion
collapsible
hover-card
```
For marketplace, chat, and support flows, also plan for these newer shadcn components:
```text
message
message-scroller
attachment
marker
```
Always use `cn()`:
```ts
export function cn(...values: Array<string | false | null | undefined>) {
return values.filter(Boolean).join(" ");
}
```
---
# 12. Choosing the Right UI Surface
Before adding a new tool, choose the right surface:
| Feature size | Placement | Example |
| ---------------------------------- | --------------------------------- | ----------------------------------- |
| 1-5 quick settings | context menu / dropdown / popover | status, due date, tags |
| 5-12 grouped settings | sectioned, scrollable popover | entity properties, compact filters |
| large data sets or bulk actions | sidebar / drawer | filters, tools panel |
| complex form or dangerous action | modal | import/export, delete confirmation |
| permanent workspace | dedicated view/page/widget | dashboard, calendar, editor |
Rule:
> If a control is used occasionally, keep it in a menu.
> If a control is used constantly, keep it visible on the main surface.
> If a control is complex and lengthy, move it to a sidebar or modal.
Do not turn a small group of controls into a large card on the page. In productivity interfaces, this wastes valuable space.
---
# 13. Compact UI for Editors, Dashboards, and Workspaces
In productivity applications, the primary content must remain the focus.
Required:
- the title, body, board, or editor must not be pushed downward by secondary controls;
- entity properties should generally open from an icon button next to the title;
- settings buttons must have an `aria-label`;
- an important status can be shown as a small badge;
- create/add actions must appear in a clear context;
- sidebar-heavy flows must include a mobile-friendly menu or switcher;
- do not make a productivity tool look like a landing page.
Bad:
```tsx
${largepropertiescard}
<Select>Status</Select>
<Select>Task</Select>
<Input>Date</Input>
<Input>Tags</Input>
</LargePropertiesCard>
```
Good:
```tsx
${titlerow}
<TitleInput />
<PropertiesMenu />
</TitleRow>
```
---
# 14. Overlays, Dropdowns, Popovers, and Context Menus
Every menu must behave as a true overlay.
Rules:
- if a menu may extend beyond its container, render it through `createPortal(..., document.body)`;
- use `position: fixed` or a reliable positioning helper;
- set an explicit `z-index`;
- use an opaque `backgroundColor`;
- do not rely only on a translucent `bg-black/50` background or blur;
- add a border, ring, or shadow;
- set `max-height` and `overflow-y-auto`;
- close on `Escape`;
- close on outside click/tap;
- prevent page text from showing through or rendering over the menu;
- hover and active states must not change the item's dimensions.
Minimal overlay style:
```tsx
<div
role="menu"
className="rounded-2xl border p-2 shadow-2xl"
style=${backgroundcolor:"#151a21",
boxShadow: "0 24px 70px rgb(0 0 0 / 78%)",
isolation: "isolate",
zIndex: 1000,}
>
...
</div>
```
If the menu background does not render correctly or content appears above it, check:
- the portal;
- `position`;
- `z-index`;
- parent stacking contexts;
- `isolation`;
- opacity/background;
- parent overflow/clipping.
---
# 15. Option Lists in Menus
A list of tasks, projects, users, tags, or other options in a menu must not look like a dense wall of text.
For a two-line item:
- use a `min-height` of 40-44px;
- include a `gap` between the icon, text, and checkmark;
- use vertical padding such as `py-1.5`;
- give the title and metadata different line heights;
- add `mt-0.5` between the title and metadata;
- apply `min-w-0` to the parent containing the text;
- apply `truncate` to the title and metadata;
- apply `shrink-0` to checkmarks and icons.
Example:
```tsx
<button className="flex min-h-11 items-center gap-2.5 rounded-lg px-2.5 py-1.5">
<span className="min-w-0 flex-1">
<span className="block truncate font-medium leading-5">${title}</span>
<span className="mt-0.5 block truncate text-xs leading-4 text-muted">
{meta}
</span>
</span>
{isActive ? <Check className="shrink-0" /> : null}
</button>
```
---
# 16. Long Text and Overflow
Any user-provided text may contain a long word with no spaces.
For editors, `contenteditable` elements, Markdown, card titles, and comments:
- use `min-w-0` on flex/grid children;
- use the current Tailwind utilities for wrapping long words;
- in newer Tailwind versions, `break-words` may be written as `wrap-break-word`;
- check the documentation for the project's current Tailwind version before using wrapping, overflow, text-wrap, grid, spacing, or arbitrary-value classes;
- if an element is inside a flex container and long text breaks its width, check whether `wrap-anywhere` is appropriate;
- use `truncate` for short lines in cards;
- wrap body text instead of allowing horizontal overflow;
- text must not render over a menu, popover, or modal;
- test with a long string containing no spaces.
For an editable block:
```tsx
className = "min-w-0 wrap-break-word whitespace-pre-wrap";
```
If the project uses an older Tailwind version where `wrap-break-word` is unavailable, check the installed Tailwind version and the official documentation or version notes, then use a supported equivalent: `break-words`, an arbitrary value, or a CSS property.
For a badge:
```tsx
className = "inline-flex whitespace-nowrap";
```
A badge must not compress text vertically. If it does not fit, move it to a new line or use `truncate` with an explicit, understandable width.
---
# 17. Tailwind CSS: Verify Current Class Names
The AI agent must check the Tailwind version installed in the project before using new or potentially version-dependent classes.
Process:
1. Inspect `package.json` and the lockfile.
2. Determine the Tailwind major version.
3. If a class may differ between versions, check the official documentation for that exact version.
4. Do not replace classes mechanically without verification.
5. When using an arbitrary value, confirm that it is included in the build output.
Pay particular attention to:
- `break-words` / `wrap-break-word` / `wrap-anywhere`;
- `text-wrap`, `text-balance`, and `text-pretty`;
- `overflow-*`;
- `size-*`;
- arbitrary colors such as `bg-[#151a21]`;
- arbitrary shadows;
- arbitrary grid templates;
- dynamic class names.
Do not build dynamic Tailwind classes like this:
```tsx
const color = "red";
return <div className={`bg-${color}-500`} />;
```
Tailwind may not detect that class during the build. Use a map:
```tsx
const colorClassName = {
danger: "bg-red-500",
success: "bg-emerald-500",
}${variant};
```
If an important overlay background must not depend on Tailwind's build output, using an inline `style.backgroundColor` is acceptable.
---
# 18. Layout and Sidebar Collapse
Collapsing a sidebar or drawer must not change the page height or leave an empty block.
Rules:
- app shell: `h-dvh min-h-dvh overflow-hidden`;
- internal regions: `flex min-h-0 flex-1 overflow-hidden`;
- enable scrolling only on the appropriate region with `overflow-y-auto`;
- when collapsing, change width/flex-basis rather than height;
- a collapsed sidebar must have a stable width;
- provide a clear control for restoring the sidebar;
- destructive or creation actions must not remain as isolated buttons without context;
- preferences may be persisted in localStorage.
Example:
```tsx
<main className="flex h-dvh min-h-dvh flex-col overflow-hidden">
<div className="flex min-h-0 flex-1 overflow-hidden">
<Sidebar className="h-full min-h-0 shrink-0" />
<section className="min-h-0 flex-1 overflow-y-auto" />
</div>
</main>
```
---
# 19. Browser APIs and localStorage
In Next.js, browser APIs are available only in Client Components.
Rules:
- a file that uses `localStorage`, `window`, `document`, drag and drop, or `contenteditable` must include `"use client"`;
- do not read `localStorage` in a Server Component;
- do not cause hydration errors with different initial values;
- wrap storage operations in `try/catch`;
- storage failures must not break the UI;
- verify persisted UI preferences after a reload;
- the build must not fail with `window is not defined`.
Example:
```tsx
const toggle = useCallback(() => {
setIsCollapsed((current) => {
const next = !current;
try {
window.localStorage.setItem(KEY, next ? "true" : "false");
} catch {
// UI still works without browser storage.
}
return next;
});
}, []);
```
Verify that:
- the default state works with empty storage;
- a reload preserves the state;
- private mode or storage errors do not break the screen;
- the build does not fail with `window is not defined`.
---
# 20. Relationships Between Tools
If one entity is linked to another, the relationship must be real:
- store it in the model;
- show it in the UI;
- clicking it opens the linked entity;
- when creating the related entity, save the relationship immediately;
- preserve the relationship during import/export;
- include the relationship in search/filter behavior when useful;
- if the related entity is deleted, show a fallback in the UI.
Do not create a decorative "Link" button if the relationship is not persisted.
---
# 21. Unified Domain Operations
Each user operation must have a single source of truth.
Do not:
- create an entity one way from the slash menu;
- create it another way from the toolbar;
- bypass validation from the command palette;
- duplicate mutation logic in the context menu.
Instead:
- keep the domain operation in one place;
- have UI components call that operation;
- use the same validation and constraints for every entry point.
---
# 23. Accessibility
Required:
- use `<button>` for actions;
- use `<a>` for navigation;
- add `aria-label` to icon-only buttons;
- provide labels for inputs;
- show a visible focus state;
- support keyboard navigation;
- close modals and popovers on `Escape`;
- close popovers on outside click;
- use a focus trap in modals;
- do not use color as the only way to communicate meaning;
- do not replace `<button>` with `${div_onclick}`.
---
# 24. Loading, Empty, and Error States
Data-driven screens must account for:
- loading;
- success;
- empty state;
- permission denied;
- network error;
- server error;
- retry.
A blank screen with no explanation is a bug.
---
# 25. Security
Required:
- keep secrets on the server only;
- use runtime validation;
- enforce access control on the server;
- validate file MIME types and sizes;
- sanitize user-provided HTML;
- do not use `dangerouslySetInnerHTML` without a sanitizer;
- do not log tokens or personal data;
- do not trust `role` or `userId` values supplied by the browser.
---
# 26. Performance
Measure first, then optimize.
Use:
- dynamic imports for heavy editor, chart, map, and PDF modules;
- image optimization;
- virtualization for large lists;
- abort/stale-request protection for search;
- selectors to reduce rerenders.
Do not add memoization without a reason.
---
# 27. Test the Design with Realistic Content
For additional guidance on interface quality, you may refer to:
- https://jakub.kr/skills/make-interfaces-feel-better
This resource is useful when polishing typography, hover states, shadows, borders, spacing, optical alignment, micro-interactions, and the overall feel of the interface.
Before completing a UI task, test it with:
- a long word with no spaces;
- a long Russian title;
- a short title;
- an empty title;
- multiple tags;
- a long list/category name;
- multiple options in a dropdown;
- active and inactive statuses;
- a date and a missing date.
Verify that:
- nothing overlaps;
- overlays cover the underlying content;
- text does not show through menus;
- badges do not compress text vertically;
- elements do not crowd each other;
- scrollbars do not cover important text;
- hover and focus states are easy to read;
- desktop and mobile widths both look correct.
---
# 28. Checks After Changes
After code changes, run:
```bash
npm run typecheck
npm run lint
npm run build
```
If the UI was changed:
- open the page in a browser;
- complete the primary user flow;
- test keyboard and mouse interaction;
- test `Escape` and outside-click behavior;
- test reloading;
- test long text;
- test a mobile viewport width;
- take a screenshot if the visual layer changed.
If browser verification is impossible, say so explicitly. Do not present `typecheck` as visual verification.
---
# 29. Git and the Working Tree
Before making changes, inspect the current state:
```bash
git status --short
```
Rules:
- do not revert someone else's changes without an explicit request;
- do not use destructive commands without explicit permission;
- do not perform unrelated refactoring;
- do not commit automatically unless the user asks you to;
- do not change line endings or reformat the entire project unnecessarily.
---
# 30. Final Report
In the final response, state:
- what changed;
- which files are important;
- which checks were run;
- what could not be verified;
- which risks remain.
Keep the report concise and honest.# React / Next.js 项目的通用说明
> 目的:使用 React + TypeScript、Next.js + TypeScript 以及 Tailwind CSS 开发各类项目时的一般规则。
> 用法:将此文件放置在新项目的根目录下,命名为 `AGENTS.md`、`CLAUDE.md` 或 `PROJECT_RULES.md`,或将其作为 AI 代理的基础指令集。
> 重要:这些说明不包含具体产品的规则。请将与单个项目相关的内容保存在单独的 `PROJECT_RULES.md` 文件中。
---
# 1. 核心原则
构建一个生产可用的应用,而不是一组彼此孤立的组件。
始终遵循以下顺序:
1. 检查当前项目结构、`package.json`、路由、UI 基础组件、状态库、hooks、校验 schema 以及项目规则。
2. 查找可以复用的现有 actions、辅助函数、schema 和组件。
3. 确定完成任务所需的最小改动。
4. 保留现有行为。
5. 端到端地实现每个新功能:模型、校验、UI、存储/导入/导出、边界情况以及验证。
6. 运行相关的检查并如实报告结果。
除非确实必要,否则不要新增依赖、抽象层、全局状态库或架构层。
默认使用 `shadcn/ui` 进行 UI 开发。没有明确理由,不要在其之上再添加另一个 UI 库。
---
# 2. 在 React 和 Next.js 之间选择
当项目需要以下能力时使用 Next.js:
- 路由;
- SEO;
- SSR / Server Components;
- Server Actions;
- Route Handlers / API 路由;
- 身份认证;
- 数据库访问;
- 私有环境变量;
- 内容发布。
在以下情况下使用 React + Vite:
- 应用完全运行在客户端;
- 不需要 SEO;
- 它是一个本地工具、仪表盘、编辑器、管理后台或类似桌面的 UI;
- 服务器已作为独立服务存在。
不要仅仅因为 Next.js 流行就选择它。没有具体原因,不要新增 Redux、Zustand、React Query、表单库或其他 UI 库。
---
# 3. 默认技术栈与检查
默认使用以下技术:
- React;
- 启用严格模式的 TypeScript;
- Tailwind CSS;
- `shadcn/ui` 作为实现简洁设计和快速界面开发所必需的 UI 方案;
- Lucide React 或当前 shadcn 配置所使用的图标库;
- ESLint;
- 共用的 `cn()` 辅助函数;
- 针对外部数据的运行时校验;
- 无障碍的 HTML 元素。
将 `shadcn/ui` 作为 UI 基础组件的主要来源:按钮、输入框、下拉框、对话框、抽屉、下拉菜单、工具提示、选项卡、轮播、卡片、徽章、骨架屏、滚动区域以及其他所需组件。仅当 shadcn 未提供合适组件,或项目已经具备稳定的本地基础组件时,才创建自定义基础组件。
对于 MVP,先使用 mock/JSON/localStorage 数据,优先验证本地用户流程。在 UI、模型和流程清晰之后,最后再添加后端、数据库、支付、身份认证和外部集成。
代码变更后,至少运行以下命令:
```bash
npm run typecheck
npm run lint
npm run build
```
如果这些命令未运行或运行出错,不要声称项目可以正常工作。
---
# 4. 架构
对于预期会增长的 Next.js 项目,默认将源代码放在 `src/` 内:`src/app`、`src/components`、`src/lib`、`src/data`、`src/hooks` 以及 `src/features`。将根级别的支持文件夹和文件(`public`、配置文件、锁文件以及 README)保留在项目根目录。
对于小型项目,可以接受以下结构:
```text
src/
app/ or pages/
components/
features/
lib/
shared/
```
对于中大型项目,使用类似 FSD 的方式:
```text
src/
app/ # 启动、providers、layouts、routes
views/ # 页面级组合
widgets/ # 大型 UI 区块
features/ # 用户流程
entities/ # 领域模型
shared/ # 通用辅助函数、配置、对 shadcn/ui 的轻量封装
```
导入方向:
```text
app/views -> widgets -> features -> entities -> shared
```
禁止:
- 在 `features` 中导入 `widgets`;
- 将业务逻辑放在 `shared` 中;
- 将 `shared/lib` 变成无关函数的堆放处;
- 在多个 UI 组件中重复相同的变更逻辑;
- 当某个模块已经对外暴露公共 API 时,进行深入其内部的深路径导入。
---
# 5. 公共 API
每个 feature、entity 或 shared UI 文件夹在被外部使用时,都应该通过 `index.ts` 暴露清晰的公共 API。对于 shadcn 基础组件,公共 API 通常已经存在于 `components/ui/*` 或项目的本地 UI 层中。
推荐:
```ts
import { createTask } from "@/features/create-task";
```
不推荐:
```ts
import { createTask } from "@/features/create-task/model/createTask";
```
例外:同一 feature 或 entity 内部的代码。
---
# 6. TypeScript
必须:
- 启用 `strict: true`;
- 除孤立的互操作代码外,不要使用 `any`;
- 不要用 `as` 断言掩盖类型错误;
- 对复杂状态使用可辨识联合类型(discriminated unions);
- 使用 schema 对运行时 JSON 进行校验;
- 不要在无合理理由的情况下创建多个相同的类型。
状态类型示例:
```ts
type LoadState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; message: string };
```
---
# 7. React 状态与副作用
将状态保存在它真正属于的地方:
| 状态类型 | 存放位置 |
| ---------- | -------------------------------------------------- |
| 本地 UI | `useState`、`useReducer` |
| URL 状态 | 路由/search 参数 |
| 服务端状态 | 服务端渲染或缓存/查询层 |
| 表单状态 | 表单 hook/库 |
| 全局 UI | 必要时使用小型状态库 |
| 领域状态 | 当状态在多个流程间共享时,放在 entity/store 中 |
不要将以下内容放入全局状态库:
- hover 状态;
- 单个下拉框的状态;
- 单个输入框的草稿值;
- 单个模态框的状态;
- 某个组件的临时选中选项卡。
使用 `useEffect` 与外部系统同步:
- 浏览器 API;
- 定时器;
- 订阅;
- 外部 store;
- DOM 集成。
不要将 `useEffect` 用于派生值。
不推荐:
```tsx
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
```
推荐:
```tsx
const fullName = `${firstName} ${lastName}`;
```
---
# 8. Next.js 边界
在 App Router 中,组件默认是 Server Component。
仅在以下需要的地方添加 `"use client"`:
- 事件处理函数;
- 本地状态;
- 副作用;
- `window`、`document` 或 `localStorage`;
- 拖放;
- `contenteditable`;
- 仅限客户端的库。
没有明确需要,不要将整个布局变成 Client Component。
仅限服务端的代码包括:
- 数据库访问;
- 身份认证;
- 私有 API 客户端;
- 私密环境变量;
- Webhooks;
- 访问权限检查。
切勿将仅限服务端的模块导入到 Client Component 中。
---
# 9. 运行时校验与迁移
在边界处校验所有外部数据:
- 请求体;
- 表单数据;
- URL/search 参数;
- 上传的文件;
- 导入的 JSON;
- localStorage/IndexedDB 数据;
- 外部 API 的响应。
添加新的模型字段时,更新完整的生命周期:
1. TypeScript 类型。
2. 运行时 schema。
3. 工厂/默认值。
4. 旧数据的解析/迁移。
5. 标准化辅助函数。
6. 导入/导出。
7. 如果该字段需要被搜索,则更新搜索/过滤索引。
8. 如果用户可以编辑该字段,则更新撤销/重做快照。
9. 用于创建、编辑和清除该字段的 UI。
10. 边界情况与检查。
示例:
```ts
return {
...item,
status: item.status ?? "active",
tags: normalizeTags(item.tags),
dueDate: normalizeDate(item.dueDate),
};
```
不要只在 UI 中添加模型字段。
---
# 10. 表单
每个表单都必须包含:
- 校验 schema;
- 字段错误提示;
- 提交中/加载状态;
- 提交时禁用的提交按钮;
- 防止重复提交;
- 错误状态;
- 成功行为;
- 在适用情况下的重置/草稿行为。
如果一个表单只有在请求完美成功时才工作,那么它就是不完整的。
---
# 11. shadcn/ui 与共享 UI
默认使用 `shadcn/ui` 来快速构建简洁、一致的界面。
规则:
- 首先检查所需的组件是否已存在于 shadcn 注册表中;
- 通过 CLI 或项目既定的本地方式添加 shadcn 组件;
- 如果 shadcn 已经覆盖了用例,不要再创建自定义的 Button、Input、Modal、Dropdown、Tooltip、Tabs 或 Card;
- 通过 `className`、variants 和组合来适配 shadcn 组件,而不是复制相似的组件;
- 将业务组件与基础组件分开:放在 `components/marketplace`、`features/*/ui`、`widgets/*` 或 `entities/*/ui`;
- 在 `components/ui` 或 `shared/ui` 中只保留 shadcn 基础组件和轻量的可复用封装;
- 不要将产品特定的业务组件放在那里;
- 如果 shadcn 没有提供该组件,则创建一个与当前 shadcn 配置一致的最小本地封装。
用于生产力界面的 shadcn 基础组件集合:
```text
button
input
select
textarea
checkbox
switch
dialog
sheet
dropdown-menu
popover
tooltip
tabs
card
badge
avatar
separator
scroll-area
skeleton
carousel
accordion
collapsible
hover-card
```
对于 marketplace、聊天和客服流程,还应规划以下较新的 shadcn 组件:
```text
message
message-scroller
attachment
marker
```
始终使用 `cn()`:
```ts
export function cn(...values: Array<string | false | null | undefined>) {
return values.filter(Boolean).join(" ");
}
```
---
# 12. 选择合适的 UI 表面
在添加新工具之前,先选择合适的表面:
| 功能规模 | 放置位置 | 示例 |
| ---------------------------- | ------------------------------ | ------------------------------------ |
| 1-5 个快速设置 | 右键菜单 / 下拉菜单 / popover | 状态、截止日期、标签 |
| 5-12 个分组设置 | 分组的、可滚动的 popover | 实体属性、紧凑的过滤器 |
| 大数据集或批量操作 | 侧边栏 / 抽屉 | 过滤器、工具面板 |
| 复杂表单或危险操作 | 模态框 | 导入/导出、删除确认 |
| 永久性工作区 | 专用视图/页面/widget | 仪表盘、日历、编辑器 |
规则:
> 如果某个控件偶尔使用,请将其放在菜单中。
> 如果某个控件经常使用,请将其直接显示在主表面上。
> 如果某个控件复杂且冗长,请将其移到侧边栏或模态框中。
不要将少量控件变成页面上的一个大卡片。在生产力界面中,这会浪费宝贵的空间。
---
# 13. 编辑器、仪表盘和工作区的紧凑 UI
在生产力应用中,主要内容必须是焦点所在。
必须:
- 标题、正文、看板或编辑器不应被次要控件向下挤压;
- 实体属性通常应通过标题旁的图标按钮打开;
- 设置按钮必须带有 `aria-label`;
- 重要状态可以以小徽章的形式展示;
- 创建/添加操作必须出现在明确的上下文中;
- 重度依赖侧边栏的流程必须包含适合移动端的菜单或切换器;
- 不要让生产力工具看起来像营销落地页。
不推荐:
```tsx
${largepropertiescard}
<Select>Status</Select>
<Select>Task</Select>
<Input>Date</Input>
<Input>Tags</Input>
</LargePropertiesCard>
```
推荐:
```tsx
${titlerow}
<TitleInput />
<PropertiesMenu />
</TitleRow>
```
---
# 14. 浮层、下拉菜单、Popover 和右键菜单
每个菜单都必须表现为真正的浮层。规则:
- 如果菜单可能超出其容器,请通过 `createPortal(..., document.body)` 进行渲染;
- 使用 `position: fixed` 或可靠的定位辅助工具;
- 设置明确的 `z-index`;
- 使用不透明的 `backgroundColor`;
- 不要仅依赖半透明的 `bg-black/50` 背景或模糊效果;
- 添加边框、ring 或阴影;
- 设置 `max-height` 和 `overflow-y-auto`;
- 按下 `Escape` 时关闭;
- 点击/点击外部时关闭;
- 防止页面文字透过菜单显示或渲染在菜单之上;
- hover 和 active 状态不得改变条目的尺寸。
极简悬浮层样式:
```tsx
<div
role="menu"
className="rounded-2xl border p-2 shadow-2xl"
style={{backgroundColor:"#151a21",
boxShadow: "0 24px 70px rgb(0 0 0 / 78%)",
isolation: "isolate",
zIndex: 1000,}}
>
...
</div>
```
如果菜单背景无法正确渲染,或内容出现在菜单之上,请检查:
- portal;
- `position`;
- `z-index`;
- 父级堆叠上下文;
- `isolation`;
- 不透明度/背景;
- 父级溢出/裁剪。
---
# 15. 菜单中的选项列表
菜单中的任务、项目、用户、标签或其他选项列表,绝不能看起来像一堵密密麻麻的文本墙。
对于两行条目:
- 使用 40-44px 的 `min-height`;
- 在图标、文字和勾选标记之间设置 `gap`;
- 使用垂直内边距,例如 `py-1.5`;
- 标题和元数据使用不同的行高;
- 在标题和元数据之间添加 `mt-0.5`;
- 在包含文字的父元素上应用 `min-w-0`;
- 对标题和元数据应用 `truncate`;
- 对勾选标记和图标应用 `shrink-0`。
示例:
```tsx
<button className="flex min-h-11 items-center gap-2.5 rounded-lg px-2.5 py-1.5">
<span className="min-w-0 flex-1">
<span className="block truncate font-medium leading-5">{title}</span>
<span className="mt-0.5 block truncate text-xs leading-4 text-muted">
{meta}
</span>
</span>
{isActive ? <Check className="shrink-0" /> : null}
</button>
```
---
# 16. 长文本与溢出
任何用户提供的内容都可能包含没有空格的长单词。
对于编辑器、`contenteditable` 元素、Markdown、卡片标题和评论:
- 在 flex/grid 子元素上使用 `min-w-0`;
- 使用当前版本的 Tailwind 工具类来换行长单词;
- 在较新版本的 Tailwind 中,`break-words` 可写作 `wrap-break-word`;
- 在使用换行、溢出、text-wrap、grid、间距或任意值类之前,请查阅项目当前 Tailwind 版本的官方文档;
- 如果某个元素位于 flex 容器内,且长文本撑破了其宽度,请评估是否适合使用 `wrap-anywhere`;
- 对卡片中的短行使用 `truncate`;
- 正文应换行显示,而不是允许水平溢出;
- 文字不得渲染在菜单、弹层或模态框之上;
- 使用没有空格的长字符串进行测试。
对于可编辑块:
```tsx
className = "min-w-0 wrap-break-word whitespace-pre-wrap";
```
如果项目使用的是较旧的 Tailwind 版本,且其中没有 `wrap-break-word`,请检查已安装的 Tailwind 版本以及官方文档或版本说明,然后使用受支持的等价类:`break-words`、任意值或对应的 CSS 属性。
对于徽标:
```tsx
className = "inline-flex whitespace-nowrap";
```
徽标不得垂直压缩文字。如果放不下,应将其换到新行,或结合明确且合理的宽度使用 `truncate`。
---
# 17. Tailwind CSS:核实当前的类名
AI Agent 在使用新的或可能依赖版本特性的类之前,必须检查项目中已安装的 Tailwind 版本。
流程:
1. 检查 `package.json` 和 lockfile。
2. 确认 Tailwind 的主版本号。
3. 如果某个类在不同版本之间可能存在差异,请查阅该确切版本的官方文档。
4. 不要在未经核实的情况下机械替换类。
5. 使用任意值时,请确认它确实包含在构建输出中。
请特别留意:
- `break-words` / `wrap-break-word` / `wrap-anywhere`;
- `text-wrap`、`text-balance` 和 `text-pretty`;
- `overflow-*`;
- `size-*`;
- 任意颜色,例如 `bg-[#151a21]`;
- 任意阴影;
- 任意网格模板;
- 动态类名。
不要像下面这样拼接动态 Tailwind 类:
```tsx
const color = "red";
return <div className={`bg-${color}-500`} />;
```
Tailwind 在构建时可能无法识别该类。应使用映射表:
```tsx
const colorClassName = {
danger: "bg-red-500",
success: "bg-emerald-500",
}[variant];
```
如果某个关键悬浮层背景不应依赖 Tailwind 的构建输出,可以使用内联的 `style.backgroundColor`。
---
# 18. 布局与侧边栏折叠
折叠侧边栏或抽屉时,不得改变页面高度,也不得留下空白区域。
规则:
- 应用外壳:`h-dvh min-h-dvh overflow-hidden`;
- 内部区域:`flex min-h-0 flex-1 overflow-hidden`;
- 仅在合适的区域使用 `overflow-y-auto` 启用滚动;
- 折叠时,调整宽度/flex-basis,而不是高度;
- 折叠后的侧边栏必须具有稳定的宽度;
- 提供明确的控件以恢复侧边栏;
- 销毁性或创建性操作不得作为孤立的按钮留在那里而失去上下文;
- 偏好设置可以保存在 localStorage 中。
示例:
```tsx
<main className="flex h-dvh min-h-dvh flex-col overflow-hidden">
<div className="flex min-h-0 flex-1 overflow-hidden">
<Sidebar className="h-full min-h-0 shrink-0" />
<section className="min-h-0 flex-1 overflow-y-auto" />
</div>
</main>
```
---
# 19. 浏览器 API 与 localStorage
在 Next.js 中,浏览器 API 仅在 Client Component 中可用。
规则:
- 使用 `localStorage`、`window`、`document`、拖放或 `contenteditable` 的文件必须包含 `"use client"`;
- 不要在 Server Component 中读取 `localStorage`;
- 不要因为初始值不同而导致 hydration 错误;
- 将存储操作包裹在 `try/catch` 中;
- 存储失败不得破坏 UI;
- 重新加载后验证持久化的 UI 偏好;
- 构建不得因 `window is not defined` 而失败。
示例:
```tsx
const toggle = useCallback(() => {
setIsCollapsed((current) => {
const next = !current;
try {
window.localStorage.setItem(KEY, next ? "true" : "false");
} catch {
// 没有浏览器存储时 UI 仍能正常工作。
}
return next;
});
}, []);
```
请验证:
- 存储为空时默认状态是否可用;
- 重新加载后状态是否被保留;
- 隐私模式或存储错误不会破坏界面;
- 构建不会因 `window is not defined` 而失败。
---
# 20. 工具之间的关系
如果一个实体关联到另一个实体,则该关系必须是真实存在的:
- 将其存入数据模型;
- 在界面上显示该关系;
- 点击它可以打开关联的实体;
- 创建关联实体时,立即保存该关系;
- 在导入/导出过程中保留该关系;
- 在有用的情况下,将该关系纳入搜索/筛选行为;
- 如果关联实体被删除,界面上应展示回退内容。
如果关系未被持久化,请勿创建一个装饰性的“链接”按钮。
---
# 21. 统一的领域操作
每一个用户操作必须具有单一事实来源。
请勿:
- 通过斜杠菜单以一种方式创建实体;
- 通过工具栏以另一种方式创建实体;
- 在命令面板中绕过校验;
- 在右键菜单中重复相同的变更逻辑。
正确做法:
- 将领域操作集中在一处维护;
- 由 UI 组件调用该操作;
- 各个入口都使用相同的校验和约束。
---
# 23. 可访问性
必备项:
- 操作使用 `<button>`;
- 导航使用 `<a>`;
- 为仅图标按钮添加 `aria-label`;
- 为输入框提供 label;
- 显示可见的 focus 状态;
- 支持键盘导航;
- 模态框和弹层按 `Escape` 关闭;
- 弹层在外部点击时关闭;
- 在模态框中使用焦点陷阱;
- 不要将颜色作为传达信息的唯一方式;
- 不要用 `${div_onclick}` 替代 `<button>`。
---
# 24. 加载、空与错误状态
由数据驱动的界面必须覆盖以下状态:
- 加载中;
- 成功;
- 空状态;
- 权限被拒绝;
- 网络错误;
- 服务器错误;
- 重试。
没有任何说明的空白界面属于缺陷。
---
# 25. 安全
必备项:
- 密钥仅保存在服务器端;
- 使用运行时校验;
- 在服务器端强制执行访问控制;
- 校验文件 MIME 类型与大小;
- 清理用户提供 HTML 内容;
- 没有清理器的情况下不要使用 `dangerouslySetInnerHTML`;
- 不要记录 token 或个人数据;
- 不要信任浏览器传入的 `role` 或 `userId`。
---
# 26. 性能
先度量,再优化。
可使用:
- 对重量级的编辑器、图表、地图和 PDF 模块使用动态导入;
- 图片优化;
- 对大列表使用虚拟化;
- 对搜索请求使用中止/过期保护;
- 使用选择器以减少重渲染。
没有理由就不要添加 memoization。
---
# 27. 使用真实内容测试设计
如需更多界面质量方面的指导,可以参考:
- https://jakub.kr/skills/make-interfaces-feel-better
在打磨排版、hover 状态、阴影、边框、间距、视觉对齐、微交互以及整体界面观感时,这份资料非常有用。
在完成一项 UI 任务前,请使用以下内容进行测试:
- 没有空格的长单词;
- 一条很长的俄语标题;
- 短标题;
- 空标题;
- 多个标签;
- 较长的列表/分类名称;
- 下拉菜单中的多个选项;
- 激活与非激活状态;
- 一个日期以及缺失日期的情况。
请验证:
- 没有元素重叠;
- 悬浮层能够覆盖下层内容;
- 文字不会透过菜单显示;
- 徽标不会垂直压缩文字;
- 元素之间不会过于拥挤;
- 滚动条不会遮挡重要文字;
- hover 和 focus 状态清晰可读;
- 桌面宽度和移动宽度下都看起来正确。
---
# 28. 修改后的检查
代码修改完成后,请运行:
```bash
npm run typecheck
npm run lint
npm run build
```
如果修改了 UI:
- 在浏览器中打开该页面;
- 走完主要的用户流程;
- 测试键盘和鼠标交互;
- 测试 `Escape` 和外部点击行为;
- 测试重新加载;
- 测试长文本;
- 测试移动端视口宽度;
- 如果涉及视觉层面,请截图。
如果无法在浏览器中验证,请明确说明。不要将 `typecheck` 视为视觉验证。
---
# 29. Git 与工作区
在动手修改之前,请检查当前状态:
```bash
git status --short
```
规则:
- 没有明确请求,不要回退他人的修改;
- 没有明确授权,不要使用破坏性命令;
- 不要进行无关的重构;
- 除非用户要求,否则不要自动提交;
- 不要无谓地更改换行符或对整个项目重新格式化。
---
# 30. 最终报告
在最终回复中,请说明:
- 修改了什么;
- 哪些文件比较关键;
- 运行了哪些检查;
- 哪些内容无法验证;
- 还存在哪些风险。
报告请保持简洁、诚实。相关资源
按类型、任务、场景与标签加权推荐
Mastra Factory
AI代理 · 工作流 · 开源框架 · TypeScript · LLM编排
Mastra 由 Gatsby 团队开发,是一个用于构建 AI 应用和代理的框架,它支持工作流、内存管理、流式处理、评估、追踪以及 Studio(一个用于开发和测试的交互式 UI)。
Harden
AI代理 · 安全加固 · 完整性 · 开发工具 · 代码审查
Harden AIF 是一款免费的本地 AI 编码代理安全工具。它采用后训练模型,利用您的请求和会话上下文,在工具调用运行前对其进行检查。在关键的代理安全基准测试中,它超越了前沿模型,同时将您的代码库和工具输出保留在您的本地计算机上。
BrionetAI
AI代理 · 企业自动化 · 多模型编排 · 私有化部署 · 工作流引擎
将问题转化为互动式学习体验。你可以获取动画讲解、多语言语音旁白、AI 生成的模拟考试、自动生成的闪卡,以及个性化的分步学习路径。
Web Search Agents by Nimble
web · search · real-time · data · AI · agent · scraping · structured
网络搜索代理是针对您特定领域(例如公司信息丰富、法规研究等)的专业网络爬虫和研究代理。它们会自主学习您的使用场景,深入挖掘对您最重要的资源,从而为您的 AI 提供更深入、更相关的网络上下文
Jolo — Your agents. One workspace.
AI代理 · 工作台 · 自动化 · 多智能体 · 协作
Jolo 是一款开源桌面应用程序和命令行界面 (CLI),用于与编码代理协作。它将 Claude Code、Codex、Devin、Gemini 和其他代理整合到一个工作区中,并包含聊天记录、文件、终端和浏览器
Demovanta
AI · Agent · 演示平台 · 评测 · 开发工具
。只需粘贴URL,一个真实浏览器就会打开你的网页,模拟浏览操作,并用你的母语旁白讲解屏幕上的内容