首页
This commit is contained in:
121
.trellis/spec/frontend/component-guidelines.md
Normal file
121
.trellis/spec/frontend/component-guidelines.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Component Guidelines
|
||||
|
||||
> This document defines the standards for creating, naming, and organizing React components in this project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
We follow a strict functional component pattern with a focus on style isolation, type safety, and clean logic separation. Every component should be modular, self-contained, and well-documented.
|
||||
|
||||
---
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### 1. File and Folder Naming
|
||||
- Use **kebab-case** (`xx-xx`) for component folders and files.
|
||||
- If the name is short, a single word is acceptable (e.g., `button.tsx`).
|
||||
- Example: `search-bar/reveal.tsx`, `user-list-item.tsx`.
|
||||
|
||||
### 2. Function Naming
|
||||
- Use **PascalCase** (`XxXx`) for the component function name.
|
||||
- Example: `const SearchBar = () => { ... }`.
|
||||
|
||||
---
|
||||
|
||||
## Component Structure
|
||||
|
||||
### 1. Functional Implementation
|
||||
All components must be written as **Functional Components**.
|
||||
|
||||
### 2. Styling & Isolation
|
||||
- **File**: Create an `index.scss` file in the same directory as the component.
|
||||
- **Root Class**: The root `div` (or container) of the component must have a **unique class name**.
|
||||
- **Isolation**: Use **SCSS nesting** with the unique root class as the parent to ensure style isolation.
|
||||
|
||||
```tsx
|
||||
// reveal.tsx
|
||||
import './index.scss';
|
||||
|
||||
export const UserCard = () => {
|
||||
return (
|
||||
<div className="user-card-wrapper">
|
||||
<h1 className="title">User Info</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
```scss
|
||||
// index.scss
|
||||
.user-card-wrapper {
|
||||
// Nested styles for isolation
|
||||
.title {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
## Props and Type Safety
|
||||
- Props Constraint: Every component with props must define a type/interface to constrain them.
|
||||
- Type Location:
|
||||
- Single Interface: If the component only needs one interface (usually the Props), define it at the top of the component file, directly below the import statements.
|
||||
- Multiple Interfaces: If there are 2 or more interfaces/types, create a types.ts file in the same directory. Move all types, including the Props interface, into this file.
|
||||
## Logic and Data Management
|
||||
### 1. Static Data Extraction
|
||||
If a component requires a large amount of static data (e.g., "dead" data for rendering, initialization configs, or constants), do not define them inside the component file.
|
||||
- Move the data to a separate .ts file in the same directory.
|
||||
- Import it using: import { CONFIG_DATA } from "./config.ts".
|
||||
### 2.Commenting Standards
|
||||
**Every method, property, interface, and complex logic block MUST be documented.**
|
||||
|
||||
- **Language Requirement**: All comments inside the code (JSDoc and internal) **MUST be written in Chinese**.
|
||||
- **Public API/Props/Interfaces**: Use JSDoc style `/** ... */` **mandatory** for every interface definition and **every single property** within that interface.
|
||||
- **Methods & Functions**: Every function (exported or internal) **must** have a `/** ... */` comment explaining its purpose, parameters, and return value.
|
||||
- **Internal Logic**: Use double-slash `//` for step-by-step explanations inside function bodies.
|
||||
```tsx
|
||||
/**
|
||||
* 用户数据
|
||||
*/
|
||||
interface UserDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UserCardProps {
|
||||
/** 用户数据详情 */
|
||||
data: UserDetail;
|
||||
}
|
||||
|
||||
export const UserCard = ({ data }: UserCardProps) => {
|
||||
/**
|
||||
*点击用户中心
|
||||
*/
|
||||
const handleProfileClick = () => {
|
||||
// 检查用户id
|
||||
if (data.id) {
|
||||
console.log('Navigating...');
|
||||
}
|
||||
};
|
||||
|
||||
return <div className="user-card" onClick={handleProfileClick}>...</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
- [ ] Folder/File name is kebab-case.
|
||||
|
||||
- [ ] Function name is PascalCase.
|
||||
|
||||
- [ ] Props are constrained by a type/interface.
|
||||
|
||||
- [ ] Styles are in index.scss using a unique root class and nesting.
|
||||
|
||||
- [ ] Large static data is moved to a separate .ts file.
|
||||
|
||||
- [ ] Proper use of /** */ for definitions and // for logic.
|
||||
|
||||
- [ ] Types are organized: in-file if single, types.ts if multiple.
|
||||
- [ ] **CRITICAL**: Every interface, property, and method must have **Chinese comments** using JSDoc (`/** */`) style.
|
||||
88
.trellis/spec/frontend/directory-structure.md
Normal file
88
.trellis/spec/frontend/directory-structure.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Directory Structure
|
||||
|
||||
> How frontend code is organized in this project, optimized for Next.js and React.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This project follows a **modular and concern-separated** structure. We prioritize **colocation** (keeping related code close to where it's used) while maintaining a clean global `src/` directory for shared resources.
|
||||
|
||||
- **Next.js App Router**: Used for routing and layouts.
|
||||
- **Component-Driven**: UI is split into atomic `common` components and module-specific `business` components.
|
||||
- **Strict Naming**: Enforces consistency across the codebase.
|
||||
|
||||
---
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```text
|
||||
src/
|
||||
├── app/ # Next.js App Router (Routes, Layouts, Pages)
|
||||
│ ├── (auth)/ # Route group (e.g., login, register)
|
||||
│ ├── dashboard/ # Dashboard module
|
||||
│ │ ├── components/ # Local components (kebab-case)
|
||||
│ │ ├── hooks/ # Local hooks (camelCase)
|
||||
│ │ ├── services/ # Local API calls/logic
|
||||
│ │ ├── page.tsx # Page entry
|
||||
│ │ └── layout.tsx # Nested layout
|
||||
│ ├── globals.css # Global styles entry
|
||||
│ └── layout.tsx # Root layout
|
||||
├── api/ # Global Axios instances and shared API clients
|
||||
├── assets/ # Static assets (images, fonts, SVGs)
|
||||
├── components/ # Global reusable UI components
|
||||
│ ├── common/ # Atomic UI components (e.g., /my-button)
|
||||
│ └── business/ # Shared business components (e.g., /user-select)
|
||||
├── hooks/ # Global custom hooks (e.g., useAuth.ts)
|
||||
├── layouts/ # Shared layout wrappers (non-routing layouts)
|
||||
├── store/ # State management (Zustand stores)
|
||||
├── styles/ # Global style configurations (Tailwind, Mixins)
|
||||
├── types/ # Global TypeScript interfaces and entities
|
||||
└── utils/ # Global utility/helper functions
|
||||
```
|
||||
## Module Organization
|
||||
### Global vs. Local
|
||||
- Global: If a component, hook, or util is used by 3 or more routes, move it to the root src/components/, src/hooks/, or src/utils/.
|
||||
- Local: If it's specific to a single feature, keep it inside that feature's directory in src/app/....
|
||||
### The app/ Directory (Routing)
|
||||
- We use Route Groups (folders with parentheses, e.g., (auth)) to organize routes without affecting the URL.
|
||||
- Each route folder should ideally contain its own page.tsx, and if complex, its own components/ sub-folder.
|
||||
|
||||
## Naming Conventions
|
||||
### Folders
|
||||
- Component Folders: Use kebab-case (e.g., src/components/common/search-bar/).
|
||||
- Other Folders: Use camelCase (e.g., src/hooks/, src/utils/, src/api/).
|
||||
- Next.js Routing: Follows Next.js standards (kebab-case for URL segments).
|
||||
### Files
|
||||
- React Components: reveal.tsx inside the component folder.
|
||||
- Hooks: use prefix with camelCase (e.g., useDebounce.ts).
|
||||
- Styles: *.module.scss or *.css (if using Scoped CSS).
|
||||
- Types: *.types.ts or types.ts.
|
||||
|
||||
## Examples
|
||||
### A Standard Business Component
|
||||
```text
|
||||
src/components/business/user-profile-card/
|
||||
├── reveal.tsx # Component logic
|
||||
├── user-avatar.tsx # Sub-component
|
||||
├── types.ts # Local types
|
||||
└── style.module.scss # Local styles
|
||||
```
|
||||
|
||||
### A Route Module
|
||||
```text
|
||||
src/app/dashboard/settings/
|
||||
├── components/ # Components only used in Settings
|
||||
│ └── profile-form/
|
||||
├── hooks/ # Hooks only used in Settings
|
||||
│ └── useSettings.ts
|
||||
├── page.tsx # Entry point for /dashboard/settings
|
||||
└── layout.tsx # Layout for settings pages
|
||||
```
|
||||
|
||||
## Path Aliases
|
||||
To avoid deep nesting like ../../../../components, use the following aliases:
|
||||
- @/* -> src/*
|
||||
- @api/* -> src/api/*
|
||||
- @comp/* -> src/components/*
|
||||
- @hooks/* -> src/hooks/*
|
||||
87
.trellis/spec/frontend/hook-guidelines.md
Normal file
87
.trellis/spec/frontend/hook-guidelines.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Hook Guidelines
|
||||
|
||||
> This document defines the standards for creating and using custom hooks to ensure logic reuse, readability, and separation of concerns.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
In this project, Hooks are the primary way to manage side effects and encapsulate business logic. We use hooks to keep components focused solely on the UI, while the "how it works" is abstracted away.
|
||||
|
||||
---
|
||||
|
||||
## Custom Hook Patterns
|
||||
|
||||
### 1. Complex Logic Extraction (Business Hooks)
|
||||
When a feature involves complex states or multiple related methods, extract them into a custom hook. A component should ideally only "call" logic, not define it.
|
||||
|
||||
- **Example (Comment Feature)**: Instead of putting the comment list, the loading state, and the `handleSubmit` function inside the component, move them to `useComments`.
|
||||
- **Benefit**: This makes the component code much cleaner and allows the logic to be tested or reused independently.
|
||||
|
||||
```tsx
|
||||
// use-comments.ts
|
||||
export const useComments = (articleId: string) => {
|
||||
const [comments, setComments] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const postComment = async (content: string) => {
|
||||
// Logic for posting a comment...
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal logic to fetch comments
|
||||
*/
|
||||
const fetchComments = async () => {
|
||||
// Logic for fetching...
|
||||
};
|
||||
|
||||
return { comments, loading, postComment, refresh: fetchComments };
|
||||
};
|
||||
```
|
||||
### 2.Utility Hooks (Shared Logic)
|
||||
Utility hooks are for common browser interactions or state monitoring that are not tied to a specific business feature.
|
||||
- `State Monitoring`: Hooks that trigger actions when a specific state changes.
|
||||
- `Event Listeners`: Hooks for window resizing, scroll detection, or clicking outside an element.
|
||||
- `Example`: useWindowSize, useDebounce, useLocalStorage.
|
||||
|
||||
----
|
||||
## Naming Conventions
|
||||
- Prefix: All hooks must start with the use prefix (e.g., useAuth, useTableData).
|
||||
- File Naming: Use kebab-case for the filename (e.g., use-comment-list.ts).
|
||||
- Function Naming: Use camelCase for the function name (e.g., useCommentList).
|
||||
|
||||
---
|
||||
## Organization
|
||||
- Global Hooks: Place reusable utility hooks in src/hooks/.
|
||||
- Feature Hooks: Place hooks specific to a single page or module within that module's own hooks/ directory (e.g., src/app/dashboard/hooks/).
|
||||
|
||||
---
|
||||
## Best Practices
|
||||
### 1.Return Types
|
||||
For hooks returning multiple values, we prefer Objects over Arrays for better extensibility and clarity.
|
||||
```tsx
|
||||
// ✅ Recommended: Object return (naming is explicit)
|
||||
const { data, loading } = useData();
|
||||
|
||||
// ❌ Avoid: Array return (unless it mimics useState)
|
||||
const [data, loading] = useData();
|
||||
```
|
||||
### 2. Memoization
|
||||
Use useCallback and useMemo within hooks for any functions or complex values that are passed as dependencies to other hooks or as props to memoized components.
|
||||
|
||||
### 3.Commenting
|
||||
Every custom hook should have a JSDoc comment explaining its purpose and the parameters it accepts.
|
||||
```tsx
|
||||
/**
|
||||
* Monitors the scroll position and returns a boolean if it exceeds a threshold.
|
||||
* @param threshold - The scroll pixel value to trigger the change.
|
||||
*/
|
||||
export const useScrollTrigger = (threshold: number) => {
|
||||
// logic...
|
||||
};
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
- Heavy Components: Putting API calls and state transitions directly in reveal.tsx instead of a hook.
|
||||
- Dependency Lies: Forgetting to include variables used inside useEffect or useCallback in the dependency array.
|
||||
- Over-abstraction: Creating a custom hook for something that is only 2 lines of code and only used once. Only extract when it adds clarity or reusability.
|
||||
51
.trellis/spec/frontend/index.md
Normal file
51
.trellis/spec/frontend/index.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Frontend Development Guidelines
|
||||
|
||||
> Best practices and coding standards for the Next.js + React project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This documentation serves as the single source of truth for frontend development within this project. It ensures consistency, maintainability, and high code quality across the team.
|
||||
|
||||
**Core Tech Stack:**
|
||||
- **Framework**: Next.js (App Router)
|
||||
- **Library**: React 18+
|
||||
- **State Management**: Zustand
|
||||
- **Styling**: SCSS (Scoped with unique root classes)
|
||||
- **Type System**: TypeScript
|
||||
|
||||
---
|
||||
|
||||
## Guidelines Index
|
||||
|
||||
| Guide | Description | Status |
|
||||
|-------|-------------|--------|
|
||||
| [Directory Structure](./directory-structure.md) | Module organization, Next.js App Router layout, and naming rules. | ✅ Active |
|
||||
| [Component Guidelines](./component-guidelines.md) | Naming, SCSS isolation, logic separation, and JSDoc standards. | ✅ Active |
|
||||
| [Hook Guidelines](./hook-guidelines.md) | Business logic extraction, utility hooks, and naming conventions. | ✅ Active |
|
||||
| [State Management](./state-management.md) | Usage of useState vs. Context vs. Zustand and persistence rules. | ✅ Active |
|
||||
| [Type Safety](./type-safety.md) | Rules for Interfaces vs. Types, Generic usage, and API typing. | ✅ Active |
|
||||
| [Quality Guidelines](./quality-guidelines.md) | Async patterns, defensive programming, and React best practices. | ✅ Active |
|
||||
|
||||
---
|
||||
|
||||
## How to Use These Guidelines
|
||||
|
||||
1. **New Features**: Before starting a new module, review the [Directory Structure](./directory-structure.md) to ensure correct placement.
|
||||
2. **Code Reviews**: Use these documents as a checklist during PR reviews to ensure all code meets the project standards.
|
||||
3. **Development**: Follow the [Component Guidelines](./component-guidelines.md) for styling and logic extraction to keep the codebase clean.
|
||||
4. **AI Assistance**: Provide these documents to AI coding assistants to help them generate code that matches our project's specific style.
|
||||
|
||||
---
|
||||
|
||||
## Maintenance
|
||||
|
||||
These guidelines are not static. If you encounter a pattern that is more efficient or discover a recurring mistake not covered here:
|
||||
- Discuss the change with the lead developer.
|
||||
- Update the relevant `.md` file with examples.
|
||||
- Notify the team of the update.
|
||||
|
||||
---
|
||||
|
||||
**Language**: All documentation and code comments must be written in **English**.
|
||||
67
.trellis/spec/frontend/quality-guidelines.md
Normal file
67
.trellis/spec/frontend/quality-guidelines.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Quality Guidelines
|
||||
|
||||
> Principles and standards to ensure code maintainability, reliability, and performance.
|
||||
|
||||
---
|
||||
|
||||
## Code Style & Consistency
|
||||
|
||||
### 1. Naming Conventions (React Specific)
|
||||
- **Event Handlers**: Use the `handle` prefix for functions that handle events (e.g., `handleSubmit`, `handleInputChange`).
|
||||
- **Callback Props**: Use the `on` prefix for props that represent events (e.g., `<Child onSuccess={handleSuccess} />`).
|
||||
- **Boolean Variables**: Use prefixes like `is`, `has`, `should` (e.g., `isLoading`, `hasError`, `shouldRender`).
|
||||
|
||||
### 2. Modern JavaScript Features
|
||||
- **Optional Chaining (`?.`)**: Always use optional chaining when accessing properties of potentially null/undefined objects (especially API responses).
|
||||
- **Nullish Coalescing (`??`)**: Use `??` instead of `||` when you specifically want to handle `null` or `undefined` but allow `0` or `""`.
|
||||
- **Destructuring**: Use destructuring for props and objects to keep code concise.
|
||||
|
||||
---
|
||||
|
||||
## Async & Data Handling
|
||||
|
||||
### 1. Asynchronous Patterns
|
||||
- **Async/Await**: Prefer `async/await` over `.then()` for better readability.
|
||||
- **Error Handling**: Every async operation must be wrapped in a `try...catch` block.
|
||||
|
||||
### 2. Waiting & Loading States
|
||||
- **User Feedback**: Every asynchronous action (like an API call) must have an associated **loading state**.
|
||||
- **Preventing Race Conditions**: Ensure that multiple rapid clicks on a "Submit" button are handled (e.g., by disabling the button during `loading`).
|
||||
|
||||
```tsx
|
||||
const handleSubmit = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await api.saveData(formData);
|
||||
// handle success
|
||||
} catch (error) {
|
||||
// handle error (e.g., show a toast)
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
```
|
||||
## React Best Practices
|
||||
### 1.Component Logic
|
||||
- Early Returns: Use early returns in functions and components to avoid deeply nested if statements.
|
||||
- Pure Functions: Keep logic that doesn't depend on React state outside the component function or in a utils file.
|
||||
### 2.Rendering Performance
|
||||
- Key Prop: Never use the array index as a key prop if the list can change (add/remove/reorder). Use unique IDs.
|
||||
- Expensive Calculations: Wrap expensive calculations in useMemo.
|
||||
### 3.Clean JSX
|
||||
- Avoid Inline Logic: If a ternary operator or logical expression in JSX is too complex, extract it into a variable or a helper function.
|
||||
- Fragment Usage: Use <>...</> (Fragments) to avoid unnecessary DOM nodes.
|
||||
---
|
||||
## Defensive Programming
|
||||
- API Robustness: Never assume the backend will return the exact data structure expected. Always provide fallbacks.
|
||||
```ts
|
||||
// ✅ Good: Defensive and clear
|
||||
const userName = data?.user?.profile?.name ?? 'Guest';
|
||||
|
||||
// ❌ Bad: Fragile
|
||||
const userName = data.user.profile.name;
|
||||
```
|
||||
## Maintenance & Documentation
|
||||
- Meaningful Comments: Don't comment what the code is doing (the code should be self-explanatory); comment why a certain non-obvious approach was taken.
|
||||
- Dead Code: Remove all console.log, commented-out code, and unused variables before submitting a Pull Request.
|
||||
- Complexity: If a component exceeds 250 lines, it is a strong signal that it needs to be refactored into smaller sub-components.
|
||||
102
.trellis/spec/frontend/state-management.md
Normal file
102
.trellis/spec/frontend/state-management.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# State Management
|
||||
|
||||
> This document outlines the strategy and standards for managing state within this project, ensuring data flows predictably and efficiently.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
We follow a tiered state management strategy. Decisions on where to store state are based on the **scope of the data** and the **nesting depth** of the components that require it.
|
||||
|
||||
---
|
||||
|
||||
## State Categories & Selection Criteria
|
||||
|
||||
### 1. Local State (`useState`)
|
||||
- **When to use**: When the state is only used within a single component or passed down to immediate children via props.
|
||||
- **Scope**: Private to the component or its direct sub-tree.
|
||||
|
||||
### 2. Module/Scoped State (`Context API`)
|
||||
- **When to use**: When state needs to be accessed by deeply nested components (e.g., Grandparent to Grandchild) within a **single functional module**, but is not needed globally.
|
||||
- **Scope**: Specific to a feature folder or a logical module.
|
||||
|
||||
### 3. Global State (`Zustand`)
|
||||
- **When to use**: When data needs to be shared across **multiple independent modules** or throughout the entire application (e.g., User Auth, Theme, Global Settings).
|
||||
- **Scope**: Application-wide.
|
||||
|
||||
---
|
||||
|
||||
## Persistence Strategy
|
||||
|
||||
To ensure a seamless user experience, we distinguish between data that should survive a page refresh and data that is volatile.
|
||||
|
||||
### 1. Persistent State (LocalStorage)
|
||||
By default, global state that represents user progress or configuration should be synchronized with **LocalStorage**.
|
||||
|
||||
### 2. Volatile State (`temp.ts`)
|
||||
For data that **should not be cached** (e.g., temporary UI toggles, scroll positions, transient navigation info), we use a dedicated store.
|
||||
- **Action**: Create a `temp.ts` file in the store directory.
|
||||
- **Usage**: All non-persistent data must reside here to avoid cluttering the browser's storage.
|
||||
|
||||
---
|
||||
|
||||
## Zustand Implementation Standards
|
||||
|
||||
When creating Zustand stores, follow the naming conventions for state and actions as shown below.
|
||||
|
||||
### Standard Pattern Example
|
||||
```typescript
|
||||
/**
|
||||
* 临时状态存储接口,用于存放不需要持久化的数据
|
||||
*/
|
||||
interface TempStore {
|
||||
/** 页面滚动条距离顶部的距离 */
|
||||
scrollTop: number;
|
||||
/** 当前路由的元信息(标题等) */
|
||||
routeInfo: routeInfo;
|
||||
/** 当前设备是否为移动端 */
|
||||
mobile: boolean;
|
||||
|
||||
/** 修改滚动条位置 */
|
||||
changeScroll: (value: number) => void;
|
||||
/** 更新路由元信息 */
|
||||
changeRouterInfo: (value: routeInfo) => void;
|
||||
/** 设置移动端状态 */
|
||||
setMobile: (value: boolean) => void;
|
||||
}
|
||||
|
||||
const useTempStore = create<TempStore>((set) => ({
|
||||
// 初始状态
|
||||
scrollTop: 0,
|
||||
routeInfo: {
|
||||
title: '首页'
|
||||
},
|
||||
mobile: false,
|
||||
|
||||
// 方法实现
|
||||
changeScroll: (value) => {
|
||||
set(() => ({ scrollTop: value }));
|
||||
},
|
||||
changeRouterInfo: (value) => {
|
||||
set(() => ({ routeInfo: value }));
|
||||
},
|
||||
setMobile: (data) => {
|
||||
set(() => ({ mobile: data }));
|
||||
}
|
||||
}));
|
||||
|
||||
```
|
||||
## Best Practices
|
||||
- Derived State: Whenever possible, calculate values on-the-fly rather than storing them in the state (e.g., don't store isLoggedIn, store the token and derive isLoggedIn = !!token).
|
||||
- Action Naming: Use clear verbs for actions:
|
||||
- changeX: For updating numerical or incremental values.
|
||||
- setX: For overwriting values or booleans.
|
||||
- Selectors: Always use selectors when consuming Zustand stores to prevent unnecessary re-renders:
|
||||
const scrollTop = useTempStore((state) => state.scrollTop);
|
||||
- Context Cleanup: When using Context, ensure the Provider is wrapped at the lowest common ancestor to minimize the re-render scope.
|
||||
- **Mandatory Documentation**: Every store interface and every single state/action property MUST have a JSDoc `/** */` comment written in **Chinese**.
|
||||
## Common Mistakes
|
||||
- **Missing Comments**: Defining a store or property without a `/** */` JSDoc comment.
|
||||
- **Wrong Language**: Writing comments in English. **All code comments must be in Chinese.**
|
||||
- **Globalizing everything**: Don't put state in Zustand just because it's "easier." If it's local to a form, use `useState`.
|
||||
- **Direct State Mutation**: Never try to mutate the state directly. Always use the `set` function provided by the store.
|
||||
148
.trellis/spec/frontend/type-safety.md
Normal file
148
.trellis/spec/frontend/type-safety.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# Type Safety
|
||||
|
||||
> Type safety patterns and conventions for this project.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This project uses TypeScript to improve code reliability and developer productivity. The approach is pragmatic: strict where it prevents common bugs, and flexible where it avoids unnecessary boilerplate.
|
||||
|
||||
---
|
||||
|
||||
## Core Rules
|
||||
### 1. Mandatory Documentation (Comments)
|
||||
**Every** interface, type alias, and enum MUST be documented using JSDoc style (`/** ... */`) comments.
|
||||
|
||||
- **Language**: All comment content **MUST be written in Chinese**.
|
||||
- **Interfaces/Types**: Describe the overall purpose in Chinese.
|
||||
- **Properties/Members**: Describe each field in Chinese, especially if its purpose isn't immediately obvious.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 主应用侧边栏的配置项
|
||||
*/
|
||||
interface SidebarConfig {
|
||||
/** 菜单项的唯一标识符 */
|
||||
id: string;
|
||||
/** 在 UI 界面显示的文本标签 */
|
||||
label: string;
|
||||
/** 可选的图标组件名称 */
|
||||
iconName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前交易的状态枚举
|
||||
*/
|
||||
enum TransactionStatus {
|
||||
/** 交易已创建但尚未处理 */
|
||||
Pending = 0,
|
||||
/** 交易已成功完成 */
|
||||
Success = 1,
|
||||
/** 交易失败或被拒绝 */
|
||||
Failed = 2,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Objects vs. Simple Types
|
||||
|
||||
- Use `interface` for object definitions and component props. This keeps object shapes extensible and produces cleaner error messages.
|
||||
- Use `type` for unions, intersections, and primitive aliases such as status codes or mode toggles.
|
||||
|
||||
```typescript
|
||||
// Use interface for objects
|
||||
interface UserInfo {
|
||||
id: string;
|
||||
userName: string;
|
||||
age: number;
|
||||
}
|
||||
|
||||
// Use type for value restrictions
|
||||
type ActiveStatus = 0 | 1;
|
||||
type ThemeMode = 'light' | 'dark';
|
||||
```
|
||||
|
||||
### 3. API Response Handling
|
||||
|
||||
To maintain development speed and avoid excessive boilerplate for backend-driven data:
|
||||
|
||||
- Use `any` for API response data when the shape is not reused across the app.
|
||||
- You do not need to define complex nested interfaces for every backend entity unless it is reused across many components as a core business model.
|
||||
|
||||
```typescript
|
||||
// ✅ Pragmatic approach for API
|
||||
async function fetchData() {
|
||||
const res = await axios.get<any>('/api/user/list');
|
||||
return res.data; // data is treated as any
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Generics (Basic Usage)
|
||||
|
||||
Use generics primarily for reusable utility functions or shared components to maintain type continuity without hardcoding types.
|
||||
|
||||
```typescript
|
||||
// Example: A generic wrapper for local storage
|
||||
function getStorageData<T>(key: string): T | null {
|
||||
const data = localStorage.getItem(key);
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
|
||||
// Usage
|
||||
const settings = getStorageData<UserInfo>('user_settings');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Type Organization
|
||||
|
||||
- Global types: place shared entities and common types in `src/types/` such as `src/types/auth.ts`.
|
||||
- Local types: place feature-specific types in a `types.ts` file within the module directory, such as `src/app/dashboard/types.ts`.
|
||||
- Documentation: Even local types must follow the mandatory comment rule.
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Component Props
|
||||
|
||||
Always define an interface for props so the component contract stays explicit.
|
||||
|
||||
```typescript
|
||||
interface SearchBarProps {
|
||||
placeholder?: string;
|
||||
onSearch: (value: string) => void;
|
||||
status: ActiveStatus;
|
||||
}
|
||||
|
||||
export const SearchBar = ({ placeholder, onSearch }: SearchBarProps) => {
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
### State Management (Zustand)
|
||||
|
||||
Define the store state and actions with an interface.
|
||||
|
||||
```typescript
|
||||
interface AuthState {
|
||||
token: string | null;
|
||||
setToken: (token: string) => void;
|
||||
clearToken: () => void;
|
||||
}
|
||||
|
||||
const useAuthStore = create<AuthState>((set) => ({
|
||||
token: null,
|
||||
setToken: (token) => set({ token }),
|
||||
clearToken: () => set({ token: null }),
|
||||
}));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Forbidden Patterns
|
||||
|
||||
- **Uncommented Types**: Never define an interface, type, or enum without a `/** */` JSDoc comment.
|
||||
- **Non-Chinese Comments**: All code documentation (JSDoc) must be in **Chinese**. Do not use English for comments.
|
||||
- **Over-engineering**: Do not create complex generic types that are hard to read. If a type takes more than 5 minutes to figure out, simplify it.
|
||||
- **Implicit Any in Props**: Never leave component props untyped. Use at least `interface Props { [key: string]: any }` if the structure is unknown.
|
||||
- **Unnecessary Type Assertions**: Avoid `as UnknownType` unless absolutely necessary.
|
||||
105
.trellis/spec/guides/code-reuse-thinking-guide.md
Normal file
105
.trellis/spec/guides/code-reuse-thinking-guide.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Code Reuse Thinking Guide
|
||||
|
||||
> **Purpose**: Stop and think before creating new code - does it already exist?
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
**Duplicated code is the #1 source of inconsistency bugs.**
|
||||
|
||||
When you copy-paste or rewrite existing logic:
|
||||
- Bug fixes don't propagate
|
||||
- Behavior diverges over time
|
||||
- Codebase becomes harder to understand
|
||||
|
||||
---
|
||||
|
||||
## Before Writing New Code
|
||||
|
||||
### Step 1: Search First
|
||||
|
||||
```bash
|
||||
# Search for similar function names
|
||||
grep -r "functionName" .
|
||||
|
||||
# Search for similar logic
|
||||
grep -r "keyword" .
|
||||
```
|
||||
|
||||
### Step 2: Ask These Questions
|
||||
|
||||
| Question | If Yes... |
|
||||
|----------|-----------|
|
||||
| Does a similar function exist? | Use or extend it |
|
||||
| Is this pattern used elsewhere? | Follow the existing pattern |
|
||||
| Could this be a shared utility? | Create it in the right place |
|
||||
| Am I copying code from another file? | **STOP** - extract to shared |
|
||||
|
||||
---
|
||||
|
||||
## Common Duplication Patterns
|
||||
|
||||
### Pattern 1: Copy-Paste Functions
|
||||
|
||||
**Bad**: Copying a validation function to another file
|
||||
|
||||
**Good**: Extract to shared utilities, import where needed
|
||||
|
||||
### Pattern 2: Similar Components
|
||||
|
||||
**Bad**: Creating a new component that's 80% similar to existing
|
||||
|
||||
**Good**: Extend existing component with props/variants
|
||||
|
||||
### Pattern 3: Repeated Constants
|
||||
|
||||
**Bad**: Defining the same constant in multiple files
|
||||
|
||||
**Good**: Single source of truth, import everywhere
|
||||
|
||||
---
|
||||
|
||||
## When to Abstract
|
||||
|
||||
**Abstract when**:
|
||||
- Same code appears 3+ times
|
||||
- Logic is complex enough to have bugs
|
||||
- Multiple people might need this
|
||||
|
||||
**Don't abstract when**:
|
||||
- Only used once
|
||||
- Trivial one-liner
|
||||
- Abstraction would be more complex than duplication
|
||||
|
||||
---
|
||||
|
||||
## After Batch Modifications
|
||||
|
||||
When you've made similar changes to multiple files:
|
||||
|
||||
1. **Review**: Did you catch all instances?
|
||||
2. **Search**: Run grep to find any missed
|
||||
3. **Consider**: Should this be abstracted?
|
||||
|
||||
---
|
||||
|
||||
## Gotcha: Asymmetric Mechanisms Producing Same Output
|
||||
|
||||
**Problem**: When two different mechanisms must produce the same file set (e.g., recursive directory copy for init vs. manual `files.set()` for update), structural changes (renaming, moving, adding subdirectories) only propagate through the automatic mechanism. The manual one silently drifts.
|
||||
|
||||
**Symptom**: Init works perfectly, but update creates files at wrong paths or misses files entirely.
|
||||
|
||||
**Prevention checklist**:
|
||||
- [ ] When migrating directory structures, search for ALL code paths that reference the old structure
|
||||
- [ ] If one path is auto-derived (glob/copy) and another is manually listed, the manual one needs updating
|
||||
- [ ] Add a regression test that compares outputs from both mechanisms
|
||||
|
||||
---
|
||||
|
||||
## Checklist Before Commit
|
||||
|
||||
- [ ] Searched for existing similar code
|
||||
- [ ] No copy-pasted logic that should be shared
|
||||
- [ ] Constants defined in one place
|
||||
- [ ] Similar patterns follow same structure
|
||||
94
.trellis/spec/guides/cross-layer-thinking-guide.md
Normal file
94
.trellis/spec/guides/cross-layer-thinking-guide.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Cross-Layer Thinking Guide
|
||||
|
||||
> **Purpose**: Think through data flow across layers before implementing.
|
||||
|
||||
---
|
||||
|
||||
## The Problem
|
||||
|
||||
**Most bugs happen at layer boundaries**, not within layers.
|
||||
|
||||
Common cross-layer bugs:
|
||||
- API returns format A, frontend expects format B
|
||||
- Database stores X, service transforms to Y, but loses data
|
||||
- Multiple layers implement the same logic differently
|
||||
|
||||
---
|
||||
|
||||
## Before Implementing Cross-Layer Features
|
||||
|
||||
### Step 1: Map the Data Flow
|
||||
|
||||
Draw out how data moves:
|
||||
|
||||
```
|
||||
Source → Transform → Store → Retrieve → Transform → Display
|
||||
```
|
||||
|
||||
For each arrow, ask:
|
||||
- What format is the data in?
|
||||
- What could go wrong?
|
||||
- Who is responsible for validation?
|
||||
|
||||
### Step 2: Identify Boundaries
|
||||
|
||||
| Boundary | Common Issues |
|
||||
|----------|---------------|
|
||||
| API ↔ Service | Type mismatches, missing fields |
|
||||
| Service ↔ Database | Format conversions, null handling |
|
||||
| Backend ↔ Frontend | Serialization, date formats |
|
||||
| Component ↔ Component | Props shape changes |
|
||||
|
||||
### Step 3: Define Contracts
|
||||
|
||||
For each boundary:
|
||||
- What is the exact input format?
|
||||
- What is the exact output format?
|
||||
- What errors can occur?
|
||||
|
||||
---
|
||||
|
||||
## Common Cross-Layer Mistakes
|
||||
|
||||
### Mistake 1: Implicit Format Assumptions
|
||||
|
||||
**Bad**: Assuming date format without checking
|
||||
|
||||
**Good**: Explicit format conversion at boundaries
|
||||
|
||||
### Mistake 2: Scattered Validation
|
||||
|
||||
**Bad**: Validating the same thing in multiple layers
|
||||
|
||||
**Good**: Validate once at the entry point
|
||||
|
||||
### Mistake 3: Leaky Abstractions
|
||||
|
||||
**Bad**: Component knows about database schema
|
||||
|
||||
**Good**: Each layer only knows its neighbors
|
||||
|
||||
---
|
||||
|
||||
## Checklist for Cross-Layer Features
|
||||
|
||||
Before implementation:
|
||||
- [ ] Mapped the complete data flow
|
||||
- [ ] Identified all layer boundaries
|
||||
- [ ] Defined format at each boundary
|
||||
- [ ] Decided where validation happens
|
||||
|
||||
After implementation:
|
||||
- [ ] Tested with edge cases (null, empty, invalid)
|
||||
- [ ] Verified error handling at each boundary
|
||||
- [ ] Checked data survives round-trip
|
||||
|
||||
---
|
||||
|
||||
## When to Create Flow Documentation
|
||||
|
||||
Create detailed flow docs when:
|
||||
- Feature spans 3+ layers
|
||||
- Multiple teams are involved
|
||||
- Data format is complex
|
||||
- Feature has caused bugs before
|
||||
79
.trellis/spec/guides/index.md
Normal file
79
.trellis/spec/guides/index.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Thinking Guides
|
||||
|
||||
> **Purpose**: Expand your thinking to catch things you might not have considered.
|
||||
|
||||
---
|
||||
|
||||
## Why Thinking Guides?
|
||||
|
||||
**Most bugs and tech debt come from "didn't think of that"**, not from lack of skill:
|
||||
|
||||
- Didn't think about what happens at layer boundaries → cross-layer bugs
|
||||
- Didn't think about code patterns repeating → duplicated code everywhere
|
||||
- Didn't think about edge cases → runtime errors
|
||||
- Didn't think about future maintainers → unreadable code
|
||||
|
||||
These guides help you **ask the right questions before coding**.
|
||||
|
||||
---
|
||||
|
||||
## Available Guides
|
||||
|
||||
| Guide | Purpose | When to Use |
|
||||
|-------|---------|-------------|
|
||||
| [Code Reuse Thinking Guide](./code-reuse-thinking-guide.md) | Identify patterns and reduce duplication | When you notice repeated patterns |
|
||||
| [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md) | Think through data flow across layers | Features spanning multiple layers |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Thinking Triggers
|
||||
|
||||
### When to Think About Cross-Layer Issues
|
||||
|
||||
- [ ] Feature touches 3+ layers (API, Service, Component, Database)
|
||||
- [ ] Data format changes between layers
|
||||
- [ ] Multiple consumers need the same data
|
||||
- [ ] You're not sure where to put some logic
|
||||
|
||||
→ Read [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md)
|
||||
|
||||
### When to Think About Code Reuse
|
||||
|
||||
- [ ] You're writing similar code to something that exists
|
||||
- [ ] You see the same pattern repeated 3+ times
|
||||
- [ ] You're adding a new field to multiple places
|
||||
- [ ] **You're modifying any constant or config**
|
||||
- [ ] **You're creating a new utility/helper function** ← Search first!
|
||||
|
||||
→ Read [Code Reuse Thinking Guide](./code-reuse-thinking-guide.md)
|
||||
|
||||
---
|
||||
|
||||
## Pre-Modification Rule (CRITICAL)
|
||||
|
||||
> **Before changing ANY value, ALWAYS search first!**
|
||||
|
||||
```bash
|
||||
# Search for the value you're about to change
|
||||
grep -r "value_to_change" .
|
||||
```
|
||||
|
||||
This single habit prevents most "forgot to update X" bugs.
|
||||
|
||||
---
|
||||
|
||||
## How to Use This Directory
|
||||
|
||||
1. **Before coding**: Skim the relevant thinking guide
|
||||
2. **During coding**: If something feels repetitive or complex, check the guides
|
||||
3. **After bugs**: Add new insights to the relevant guide (learn from mistakes)
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Found a new "didn't think of that" moment? Add it to the relevant guide.
|
||||
|
||||
---
|
||||
|
||||
**Core Principle**: 30 minutes of thinking saves 3 hours of debugging.
|
||||
Reference in New Issue
Block a user