@clawhub-goldath-0656adbb74
Use when organizing reading notes from books, articles, or papers into structured summaries and knowledge maps. Helps extract key insights, connect ideas acr...
---
name: reading-notes
description: Use when organizing reading notes from books, articles, or papers into structured summaries and knowledge maps. Helps extract key insights, connect ideas across sources, and build a personal knowledge base using proven note-taking frameworks like Zettelkasten and Cornell method.
---
# Reading Notes Organizer & Knowledge Graph Builder
Turn scattered reading notes into a connected, searchable knowledge base.
## When to Use
- After finishing a book/article/paper
- Building a second brain or knowledge repository
- Preparing for writing, research, or presentations
- Connecting ideas across multiple sources
## Core Workflow
### Step 1: Capture (边读边记)
Use the **3-Layer Capture** method:
1. **Highlight** — verbatim quotes worth keeping
2. **Summary** — your own words for each chapter/section
3. **Reaction** — your thoughts, questions, disagreements
### Step 2: Processing Template
```markdown
# 📚 读书笔记 | Reading Note
## 基本信息
- **书名/文章:** [标题]
- **作者:** [作者名]
- **类型:** 书籍 / 文章 / 论文 / 视频
- **阅读日期:** YYYY-MM-DD
- **评分:** ⭐⭐⭐⭐⭐ (1-5)
- **推荐给:** [适合什么类型的读者]
---
## 一句话总结
> [用一句话概括核心观点,< 50字]
## 核心主张 (3-5条)
1. [主张1]: [简要解释]
2. [主张2]: [简要解释]
3. [主张3]: [简要解释]
---
## 章节摘要
### [章节名]
- **关键观点:** [1-2句]
- **重要引用:** "[原文摘录]" (p.XX)
- **我的理解:** [自己的解读]
---
## 金句收藏 | Highlights
> "[精彩句子]" — 作者名, p.XX
> "[另一句]"
---
## 知识连接 | Connections
- 与《[其他书名]》的关联: [如何呼应或对比]
- 印证了 [某个理论/概念]: [说明]
- 挑战了 [某个观念]: [说明]
---
## 行动清单 | Action Items
- [ ] [受书启发想做的事1]
- [ ] [想深入研究的方向]
- [ ] [想分享给谁]
---
## 标签 | Tags
#[主题1] #[主题2] #[领域]
```
### Step 3: 知识图谱构建
为每本书创建节点,连接相关概念:
```
[书名] → 核心概念A → 相关书籍X
↓
核心概念B → 相关书籍Y
↓
核心概念C → 实践项目Z
```
**连接类型:**
- `支持` — 两本书观点互相印证
- `反驳` — 观点相互对立,值得深思
- `延伸` — 一本书是另一本的深化
- `应用` — 理论书 → 实践案例
### Step 4: Zettelkasten 原子笔记法
每个概念/想法独立成卡片:
```markdown
## 卡片 ID: YYYYMMDD-001
**概念:** [一个明确的想法或观点]
**内容:**
[2-5句话解释这个概念,用自己的话]
**来源:** 《书名》p.XX / [作者]
**链接:**
- [[相关卡片ID-1]] — [关联说明]
- [[相关卡片ID-2]] — [关联说明]
**标签:** #概念类别 #领域
```
### Step 5: 定期复习系统
| 复习周期 | 内容 |
|----------|------|
| 读完当天 | 写完整笔记,填行动清单 |
| 1周后 | 回顾核心主张,补充连接 |
| 1个月后 | 检查行动项完成情况 |
| 季度末 | 整理同主题书籍,生成主题综述 |
## 输出示例:主题综述
当积累同主题3本以上读书笔记后,生成综述:
```markdown
# 主题综述:[主题名称]
## 共识观点(多书印证)
1. [所有书都认同的核心观点]
## 争议观点(观点分歧)
- 甲方(书A, 书B): [观点]
- 乙方(书C): [观点]
- 我的判断: [综合结论]
## 推荐阅读顺序
1. 入门: 《书名》— 理由
2. 进阶: 《书名》— 理由
3. 深度: 《书名》— 理由
```
## 工具推荐
| 工具 | 用途 | 适合人群 |
|------|------|----------|
| Obsidian | 双链笔记+知识图谱 | 重度笔记用户 |
| Notion | 数据库+模板 | 偏好结构化 |
| 飞书文档 | 团队共享 | 职场协作 |
| 微信读书 | 电子书+划线 | 移动端阅读 |
FILE:references/book-note-examples.md
# Book Note Examples(读书笔记示例)
## 示例1:《原则》- 瑞·达利欧
```markdown
# 《原则》Principles
**作者:** 瑞·达利欧 (Ray Dalio)
**类型:** 商业/自我管理
**阅读日期:** 2024-03-15
**评分:** ⭐⭐⭐⭐⭐
**推荐给:** 创业者、管理者、对决策系统感兴趣的人
---
## 一句话总结
> 通过建立系统化的决策"原则",可以将主观判断转化为可复制的成功模式。
## 核心主张
1. **极度透明原则**: 组织内部的激烈坦诚优于表面和谐
2. **可信度加权决策**: 不同决策应该赋予相应专业人士更高权重
3. **拥抱痛苦**: 痛苦+反思=进步,逃避痛苦=停滞
4. **机器思维**: 把自己的生活/公司当成机器来优化
5. **五步流程**: 目标→问题→诊断→方案→执行
## 金句收藏
> "Pain + Reflection = Progress"(痛苦+反思=进步)
> "Don't let ego get in the way of your ability to make the best possible decisions."
## 知识连接
- 与《系统之美》的关联: 都强调系统思维,达利欧用系统视角看组织
- 印证了《思考,快与慢》中的"慢思考"价值: 建立原则就是系统化慢思考
- 与《从0到1》对比: 彼得·蒂尔强调秘密和差异化,达利欧强调系统和原则
## 行动清单
- [ ] 写下5条个人工作原则
- [ ] 在团队中尝试"极度透明"的反馈会议
- [ ] 建立个人决策记录日志
```
---
## 示例2:《深度工作》- 卡尔·纽波特
```markdown
# 《深度工作》Deep Work
**作者:** 卡尔·纽波特 (Cal Newport)
**类型:** 效率/学习
**阅读日期:** 2024-01-20
**评分:** ⭐⭐⭐⭐
**推荐给:** 知识工作者、创作者、学生
---
## 一句话总结
> 在注意力分散的时代,能够进行深度专注工作是最稀缺也最有价值的技能。
## 核心主张
1. **深度工作 vs 浮浅工作**: 深度=推动认知极限;浮浅=可被打断、低认知
2. **深度工作稀缺**: 开放办公室+即时消息=深度能力萎缩
3. **深度工作有价值**: 学习困难技能 + 高质量产出需要深度
4. **4种深度工作哲学**: 隐士、双峰、节律、新闻记者
## 四种深度工作哲学对比
| 哲学 | 描述 | 适合人群 |
|------|------|----------|
| 隐士式 | 几乎完全隔绝外界 | 作家、研究员 |
| 双峰式 | 部分时间完全投入深度 | 学者兼教授 |
| 节律式 | 每天固定深度时间段 | 多数知识工作者 |
| 新闻记者式 | 随时切换,按需深度 | 经验丰富的专业人士 |
## 知识连接
- 与《心流》的关联: 深度工作状态≈心流状态,都需要挑战与技能匹配
- 延伸《注意力商人》: 互联网经济本质是贩卖注意力,深度工作是反抗
- 应用于: [[第二大脑搭建]] [[个人学习系统]]
## 行动清单
- [ ] 选择适合自己的深度工作哲学
- [ ] 建立深度工作时间记录(目标:每周15小时)
- [ ] 设定"数字排毒"规则:工作日晚10点后不看手机
- [ ] 在日历中block深度工作时间段
```
---
## 示例3:英文文章笔记(短格式)
```markdown
# Article: "The Paradox of Choice" - Barry Schwartz (TED Talk)
**Source:** TED Talk 2005 / Book Summary
**Date:** 2024-04-10
**Rating:** ⭐⭐⭐⭐
**Type:** Psychology / Decision Making
---
## One-line Summary
> More choices = less happiness; constraint can be liberating.
## Key Points
1. **Maximizer vs Satisficer**: Maximizers seek the best; Satisficers seek "good enough"
- Maximizers are less happy despite better outcomes
2. **Opportunity Cost Imagination**: More options = more regret about unchosen options
3. **Adaptation Problem**: We adapt to good outcomes, always wanting more
4. **Raised Expectations**: More choice → higher expectations → more disappointment
## Connection
- Supports [[paradox-of-choice]] concept in [[Thinking Fast and Slow]]
- Contradicts conventional wisdom: "more options = more freedom"
- Applies to: product design (reduce choices), personal decisions
## Action Items
- [ ] Practice "good enough" decisions for low-stakes choices
- [ ] Limit daily decisions: preset meals on weekdays
```
---
## 读书效率提升Tips
1. **不要追求完美笔记** — 60分的笔记立刻完成 > 100分的笔记永远拖延
2. **当天处理** — 读完当天整理,隔天记忆流失50%+
3. **只摘录真正打动你的内容** — 3条深刻洞见 > 20条泛泛摘录
4. **写"为什么重要"** — 不只摘原文,写下"这对我意味着什么"
5. **定期输出** — 笔记不输出就是囤积,每月写一篇基于读书的文章/推文
FILE:references/knowledge-graph-guide.md
# Knowledge Graph Building Guide
## Why Build a Knowledge Graph
传统线性笔记的问题:
- 信息孤立,无法发现跨书联系
- 难以快速检索
- 复习时需要重读大量内容
知识图谱的优势:
- 可视化概念间关系
- 一个概念连接多个来源
- 写作时快速找到相关素材
- 发现意想不到的知识连接
---
## Building Blocks
### Node Types(节点类型)
```
📚 书籍/文章节点 —— 阅读来源
💡 概念节点 —— 核心思想/理论
👤 人物节点 —— 作者/思想家
🗂️ 主题节点 —— 知识领域分类
✍️ 洞见节点 —— 个人原创想法
🔗 项目节点 —— 实际应用场景
```
### Edge Types(连接类型)
```
支持 A → B "A的观点支持B"
反驳 A ✕ B "A与B存在矛盾"
延伸 A → B "B是A的深化/具体化"
应用 A → B "A的理论在B中有实践案例"
来源 A → B "A的思想来源于B"
启发 A → B "A启发了关于B的思考"
```
---
## Obsidian Knowledge Graph Setup
### File Naming Convention
```
books/《书名》-作者.md
concepts/概念名称.md
people/姓名.md
topics/主题名称.md
insights/YYYYMMDD-洞见标题.md
```
### Book Note Template for Obsidian
```markdown
---
tags: [books, 领域标签]
author: 作者名
year: 出版年
rating: 4
status: read
---
# 《书名》
## 核心论点
[[概念A]] — [简要解释在书中的应用]
[[概念B]] — [简要解释]
## 与其他书的关联
- 与 [[《相关书1》]] 的关联:[说明]
- 挑战了 [[《相关书2》]] 中的 [[某观点]]
## 原创洞见
> [[YYYYMMDD-洞见1]]
## 精选引用
> "原文引用" — p.XX
```
### Concept Note Template
```markdown
---
tags: [concepts, 领域]
---
# 概念名称
## 定义
[用自己的话2-3句话定义]
## 来源
- 首次了解: [[《书名》]] p.XX
- 另见: [[《相关书》]]
## 实例
1. [具体案例1]
2. [具体案例2]
## 相关概念
- [[相关概念1]] — [关系说明]
- [[相关概念2]] — [关系说明]
## 应用
[[项目/场景]] — 如何应用这个概念
```
---
## Knowledge Graph Visualization
### Obsidian Graph View Settings
```
节点大小: 按链接数量
颜色分组:
- 书籍: 蓝色
- 概念: 橙色
- 人物: 绿色
- 洞见: 紫色
- 项目: 红色
过滤: 排除 MOC(目录)页面
```
### Cluster Analysis(聚类分析)
定期检查知识图谱:
- **孤立节点**: 未连接的笔记 → 寻找连接机会
- **高度连接节点**: 核心概念 → 考虑写一篇深度文章
- **弱连接区域**: 知识盲区 → 考虑补充阅读
---
## Topic Map (主题综述) Template
当同一主题积累5+本书后:
```markdown
# 主题地图:[主题名称]
## 核心概念网络
[概念A] ←→ [概念B] ←→ [概念C]
↓
[概念D]
## 主要思想流派
### 流派1:[名称]
代表书籍: [[书A]], [[书B]]
核心主张:
我的评价:
### 流派2:[名称]
代表书籍: [[书C]]
核心主张:
与流派1的分歧:
## 我的综合观点
[基于多书阅读形成的个人立场]
## 推荐阅读路径
1. **入门**: [[书名]] — 理由
2. **进阶**: [[书名]] — 理由
3. **批判性读本**: [[书名]] — 理由
## 待深入探索
- [ ] [尚未阅读但应该读的书]
- [ ] [待回答的核心问题]
```
---
## Weekly Knowledge Review Routine
```markdown
## 每周知识回顾(15分钟)
### 本周新增笔记
- [ ] 检查孤立笔记,添加链接
- [ ] 更新主题索引页
### 行动项检查
- [ ] 上周读书行动项完成情况
### 月度主题综述更新
(每月第一周进行)
- [ ] 更新相关主题地图
- [ ] 输出一篇公开分享文章/推文
```
FILE:references/note-taking-frameworks.md
# Note-Taking Frameworks Reference
## 1. Zettelkasten Method (卡片笔记法)
### Core Principles
- **原子性**: 每张卡片只包含一个想法
- **自主性**: 卡片在脱离上下文时仍有意义
- **链接性**: 通过链接而非层级来组织知识
- **无分类**: 不预设文件夹分类,靠标签和链接
### Card Types
**Fleeting Notes (闪念笔记)**
- 随时记录的想法,未经处理
- 不超过24-48小时后必须处理或丢弃
- 工具: 备忘录、微信文件传输助手
**Literature Notes (文献笔记)**
- 阅读时的记录,写在参考资料旁
- 用自己的话,不抄原文
- 记录出处(书名、页码)
**Permanent Notes (永久笔记)**
- 经过思考后的独立卡片
- 与其他卡片建立链接
- 写给"未来的自己",确保清晰
### Zettelkasten Card Template
```markdown
ID: 20240427-001
Title: [概念名称]
[2-3句话用自己的话解释这个概念]
与 [[20240420-015]] 相关:[关联说明]
与 [[20240301-003]] 对比:[对比说明]
来源:《书名》p.XX by 作者
标签:#概念类别 #领域
```
---
## 2. Cornell Note Method (康奈尔笔记法)
### Page Layout
```
┌─────────────────────────────────┐
│ 标题 / Title │
│ 日期 / Date: YYYY-MM-DD │
├──────────────┬──────────────────┤
│ 关键词区 │ 笔记区 │
│ Keywords │ Notes │
│ (25%) │ (75%) │
│ │ │
│ • 概念1 │ 概念1的详细解释... │
│ • 概念2 │ │
│ • 问题1? │ 相关内容... │
│ │ │
│ │ │
├──────────────┴──────────────────┤
│ 总结区 Summary (≤5句话) │
│ [本页内容的核心要点] │
└─────────────────────────────────┘
```
### When to Use
- 课程/讲座笔记
- 密集内容的书籍章节
- 需要快速复习的资料
---
## 3. Mind Map (思维导图)
### Structure for Book Notes
```
《书名》
│
┌────────────┼────────────┐
核心论点 结构框架 关键概念
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
论点1 论点2 第一部分 第二部分 概念A 概念B
│ │ │
证据/例子 子主题 定义/解释
```
### Digital Tools
- Xmind: 专业思维导图
- Obsidian Canvas: 双链+可视化
- Miro: 协作白板
- draw.io: 免费在线
---
## 4. SQ3R Reading Method
**Survey(浏览)**: 5分钟快速扫描目录、标题、图表
**Question(提问)**: 将标题转化为问题("这章讲了什么?为什么重要?")
**Read(精读)**: 带着问题阅读,寻找答案
**Recite(复述)**: 合上书,用自己的话回答之前的问题
**Review(复习)**: 检验理解,与已知知识连接
### SQ3R Worksheet Template
```markdown
书名: ___________ 章节: ___________ 日期: ___________
## Survey(5分钟浏览摘要)
本章主要涵盖:
## Questions(阅读前提问)
1. [将章节标题转化为问题]
2.
3.
## Read & Recite(阅读后回答)
Q1答案:
Q2答案:
Q3答案:
## Review(总结与连接)
最重要的3个收获:
1.
2.
3.
与已知知识的连接:
```
---
## 5. Progressive Summarization (渐进式摘要)
### 5 Layers of Highlighting
| Layer | 动作 | 颜色建议 |
|-------|------|----------|
| Layer 1 | 第一遍阅读,划出感兴趣的段落 | 黄色 |
| Layer 2 | 第二遍,加粗Layer 1中最重要的句子 | 无色(加粗) |
| Layer 3 | 高亮Layer 2中的关键短语 | 蓝色 |
| Layer 4 | 用自己的话写一段摘要 | 文字 |
| Layer 5 | 创建可分享的笔记/文章 | 成品 |
**原则**: 只有在需要用到这条笔记时才进行下一层处理,避免过度整理。
Use when you need to organize meeting notes, extract action items, and generate structured summaries. Ideal for processing raw meeting transcripts or bullet...
---
name: meeting-notes
description: Use when you need to organize meeting notes, extract action items, and generate structured summaries. Ideal for processing raw meeting transcripts or bullet notes into clean, shareable documents with clear owners and deadlines.
---
# Meeting Notes Organizer & Action Item Extractor
Transform raw meeting notes into structured, actionable summaries.
## When to Use
- After any meeting to create a clean summary
- Processing audio transcripts from Zoom/Teams/飞书
- Weekly standups, sprint reviews, project kick-offs
- Turning messy notes into shareable team documents
## Core Workflow
### Step 1: Input Collection
Provide any of:
- Raw bullet-point notes
- Audio transcript text
- Voice memo content
- Email thread summary
### Step 2: Meeting Structure Template
```markdown
## 会议纪要 | Meeting Summary
**会议主题 / Topic:** [填写]
**日期 / Date:** YYYY-MM-DD
**参会人 / Attendees:** [姓名列表]
**主持人 / Facilitator:** [姓名]
**记录人 / Note-taker:** [姓名]
---
## 议题讨论 | Discussion Points
### 1. [议题标题]
- **背景:** 简要说明
- **讨论内容:** 关键讨论点
- **结论/决议:** 明确的决定
### 2. [议题标题]
...
---
## 行动项 | Action Items
| # | 任务 | 负责人 | 截止日期 | 优先级 | 状态 |
|---|------|--------|----------|--------|------|
| 1 | [任务描述] | @姓名 | MM-DD | 高/中/低 | 待开始 |
| 2 | | | | | |
---
## 待确认事项 | Open Questions
- [ ] [问题1] — 负责跟进:@姓名
- [ ] [问题2]
---
## 下次会议 | Next Meeting
**时间:** [日期时间]
**议题预告:** [下次讨论的主要议题]
```
### Step 3: Action Item Extraction Rules
When extracting action items, look for:
- **动词短语**: "需要"、"要"、"将"、"负责"、"跟进"
- **English triggers**: "will", "need to", "action:", "owner:", "TODO"
- **Implicit owners**: If someone proposed something, they likely own it
- **Deadlines**: Extract explicit dates; if none, flag as "TBD"
### Step 4: Priority Classification
```
高优先级 (High): 影响下次会议、有明确截止日期、阻塞其他任务
中优先级 (Medium): 本周内需完成、依赖关系中等
低优先级 (Low): 长期改进、无明确截止日期
```
### Step 5: Distribution Checklist
- [ ] 发送给所有参会人
- [ ] 同步到项目管理工具(Jira/飞书/Notion)
- [ ] 在下次会议前 review 行动项完成情况
- [ ] 未完成项自动滚动到下次会议
## Output Formats
**Slack/飞书快速摘要:**
```
📋 [会议主题] 纪要 - YYYY-MM-DD
✅ 决议:[1-2句核心决定]
📌 行动项(共N项):
· @张三 - [任务] - 截止 MM-DD
· @李四 - [任务] - 截止 MM-DD
❓ 待确认:[未解决问题数]
完整纪要:[链接]
```
## Pro Tips
1. **会议开始前** 明确记录人,确保覆盖所有行动项
2. **实时确认** 行动项负责人,不要会后猜测
3. **48小时原则** 会议结束48小时内发出纪要
4. **版本控制** 大型会议纪要建议保存版本历史
FILE:references/action-item-extraction.md
# Action Item Extraction Guide
## Trigger Phrase Recognition
### 中文触发词
```
强触发(明确行动):
- "XXX负责..."
- "XXX来..."
- "让XXX..."
- "由XXX跟进..."
- "需要XXX..."
- "XXX你来..."
- "行动项:..."
- "TODO: ..."
弱触发(隐含行动,需判断):
- "XXX说会..."
- "我们应该..."
- "可以考虑..."
- "最好能..."
- "理想状态是..."
```
### English Triggers
```
Strong triggers:
- "[Name] will..."
- "Action item: ..."
- "Owner: ..."
- "[Name] to ..."
- "Let's have [Name] ..."
- "TODO: ..."
- "AP: ..." (action point)
Weak triggers (needs judgment):
- "[Name] mentioned..."
- "We should probably..."
- "It would be good if..."
- "Someone needs to..."
```
---
## Extraction Rules
### Rule 1: One Task = One Row
❌ Wrong: "张三负责整理文档并发给大家还要更新Jira"
✅ Right:
- 张三:整理会议文档
- 张三:发送文档给参会人
- 张三:更新Jira状态
### Rule 2: Owner Assignment Priority
1. **Explicit mention** — "张三负责" → 张三
2. **Context owner** — "产品需要更新需求文档" → PM负责
3. **Proposer owns it** — 某人提出的改进 → 提出者跟进
4. **Role-based** — "前端需要修改" → 前端负责人
5. **Unknown** → 标记 "TBD" + 指派会议主持人跟进
### Rule 3: Deadline Extraction
```
明确日期: "周五之前" → 本周五日期
相对日期: "明天" → 会议日期+1天
模糊截止: "尽快" → 标记 ASAP,建议3个工作日
无截止日期 → 标记 TBD,优先级降为P2
```
### Rule 4: Priority Classification
```
P0 (立即) — 影响上线/发布/关键路径
P1 (本周) — 下次会议前必须完成
P2 (下周) — 重要但不紧急
P3 (待定) — 未来考虑,无明确时间
```
---
## Processing Raw Notes Example
**原始记录:**
```
讨论了首页改版,王芳说设计稿下周二能出来,
开发评估大概需要3天,李明说接口文档还没写,
测试环境服务器的事之前一直没人处理,会后找运维。
老板说要写个市场分析报告给投资人看,月底需要。
```
**提取结果:**
| # | 任务 | 负责人 | 截止日期 | 优先级 |
|---|------|--------|----------|--------|
| 1 | 完成首页改版设计稿 | 王芳 | 下周二 | P1 |
| 2 | 前端开发评估首页改版工时 | 李明/前端 | 设计稿出后 | P1 |
| 3 | 编写接口文档 | 李明 | TBD | P1 |
| 4 | 联系运维处理测试环境服务器 | 会议主持人 | 本周内 | P0 |
| 5 | 撰写市场分析报告 | TBD | 月底 | P1 |
---
## Automated Processing Prompt Template
```
请从以下会议记录中提取所有行动项。
要求:
1. 识别所有明确或隐含的任务
2. 为每个任务指定负责人(无法确定标记TBD)
3. 提取或推断截止日期
4. 按 P0/P1/P2/P3 分优先级
5. 输出为Markdown表格
会议日期:[DATE]
参会人:[NAMES]
原始记录:
[PASTE NOTES HERE]
```
FILE:references/integration-tools.md
# Meeting Notes Integration & Tools Guide
## Tool Integrations
### 飞书(Lark)集成
**飞书文档模板设置:**
1. 飞书文档 → 模板库 → 新建模板
2. 使用 SKILL.md 中的会议纪要模板
3. 设置权限:参会人均可编辑
**飞书会议纪要自动创建:**
```
飞书日历 → 会议事件 → 关联文档
→ 会后自动提醒创建纪要
→ 纪要链接自动同步到日历事件
```
**飞书Bot发送摘要:**
```json
{
"msg_type": "interactive",
"card": {
"header": {
"title": { "content": "📋 会议纪要|{会议主题}", "tag": "plain_text" },
"template": "blue"
},
"elements": [
{
"tag": "div",
"text": { "content": "**日期:** {日期}\n**参会:** {人员列表}", "tag": "lark_md" }
},
{
"tag": "div",
"text": { "content": "**核心决议:**\n{决议列表}", "tag": "lark_md" }
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": { "content": "查看完整纪要", "tag": "plain_text" },
"url": "{纪要链接}",
"type": "primary"
}
]
}
]
}
}
```
---
### Notion 集成
**Database 结构:**
```
会议记录数据库字段:
- 会议名称 (Title)
- 日期 (Date)
- 参会人 (Multi-select / People)
- 会议类型 (Select: 例会/评审/复盘/一对一)
- 项目 (Relation → 项目数据库)
- 行动项数量 (Formula: length(行动项))
- 状态 (Select: 草稿/已发送/已归档)
- 标签 (Multi-select)
```
**关联行动项数据库:**
```
行动项数据库字段:
- 任务描述 (Title)
- 来源会议 (Relation → 会议记录)
- 负责人 (Person)
- 截止日期 (Date)
- 优先级 (Select: P0/P1/P2/P3)
- 状态 (Select: 待开始/进行中/已完成/已取消)
- 备注 (Text)
```
---
### Jira / Linear 集成
**会议行动项 → Jira Issue 映射:**
```
行动项字段 → Jira字段
任务描述 → Summary
负责人 → Assignee
截止日期 → Due Date
优先级 P0 → Priority: Blocker
优先级 P1 → Priority: Critical
优先级 P2 → Priority: Major
优先级 P3 → Priority: Minor
来源会议 → Description(附链接)
```
**批量创建 Jira Issues(Python示例):**
```python
import requests
def create_jira_issues_from_meeting(action_items, jira_config):
headers = {
"Authorization": f"Bearer {jira_config['token']}",
"Content-Type": "application/json"
}
for item in action_items:
payload = {
"fields": {
"project": {"key": jira_config["project_key"]},
"summary": item["task"],
"assignee": {"accountId": item["owner_id"]},
"duedate": item["deadline"],
"priority": {"name": map_priority(item["priority"])},
"description": {
"type": "doc",
"content": [{
"type": "paragraph",
"content": [{"type": "text", "text": f"来自会议:{item['meeting_link']}"}]
}]
}
}
}
response = requests.post(
f"{jira_config['base_url']}/rest/api/3/issue",
json=payload,
headers=headers
)
print(f"Created: {response.json().get('key')} - {item['task']}")
```
---
## AI-Assisted Meeting Notes Workflow
### Step-by-Step with AI
```
1. 录音/转录 → Otter.ai / 飞书妙记 / Zoom AI Summary
2. 粘贴转录文本,使用此 Prompt:
"请将以下会议转录整理为结构化纪要,
包含:核心决议3-5条、行动项表格(含负责人/截止/优先级)、
待确认问题列表。语言:中文。"
3. 检查并补充遗漏项
4. 发送给参会人确认(24小时内)
5. 将行动项同步到项目管理工具
```
---
## Meeting Notes Quality Checklist
发送前自查:
- [ ] 每个行动项都有明确负责人
- [ ] 每个行动项都有截止日期(或标注TBD+原因)
- [ ] 核心决议已用粗体/标注突出
- [ ] 参会人姓名无错别字
- [ ] 文档权限已设置(参会人可访问)
- [ ] 已在48小时内发送
- [ ] 下次会议时间已确认并发出邀请
FILE:references/templates-cn.md
# 会议纪要模板集合(中文)
## 模板1:日常项目例会
```markdown
# 项目例会纪要
**项目名称:** _______________
**会议时间:** YYYY年MM月DD日 HH:MM-HH:MM
**会议地点/方式:** □线下 □腾讯会议 □飞书 □钉钉
**参会人员:**
**缺席人员:**
**主持人:**
**记录人:**
---
### 上次行动项跟进
| 任务 | 负责人 | 原截止日期 | 状态 | 备注 |
|------|--------|------------|------|------|
| | | | □完成 □延期 □取消 | |
---
### 本次议题
**1. [议题1]**
- 讨论内容:
- 结论/决定:
- 遗留问题:
**2. [议题2]**
- 讨论内容:
- 结论/决定:
- 遗留问题:
---
### 行动项汇总
| 序号 | 任务描述 | 负责人 | 截止日期 | 优先级 |
|------|----------|--------|----------|--------|
| 1 | | | | P0/P1/P2 |
| 2 | | | | |
---
### 下次例会
**时间:** _______________
**预计议题:** _______________
```
---
## 模板2:需求评审会
```markdown
# 需求评审会纪要
**需求名称:** _______________
**产品负责人:** _______________
**评审日期:** _______________
### 需求概述
[1-2句话描述需求背景和目标]
### 评审意见汇总
| 模块 | 评审意见 | 提出人 | 处理方式 | 状态 |
|------|----------|--------|----------|------|
| | | | □接受 □拒绝 □待讨论 | |
### 技术风险点
1. [风险描述] — 影响程度:高/中/低 — 应对方案:
2.
### 评审结论
□ 通过,可进入开发
□ 有条件通过(需解决以下问题):
□ 不通过,需重新评审
### 修改要求(如不通过)
1.
2.
```
---
## 模板3:复盘会议
```markdown
# 项目复盘会议纪要
**复盘项目/Sprint:** _______________
**时间范围:** _______________至_______________
**参会人:** _______________
---
### 数据回顾
| 指标 | 目标 | 实际 | 差异 |
|------|------|------|------|
| | | | |
---
### 做得好的地方(Keep)
1.
2.
3.
### 需要改进的地方(Improve)
1.
2.
3.
### 下次尝试的事(Try)
1.
2.
---
### 改进行动项
| 改进点 | 具体行动 | 负责人 | 下个周期验收标准 |
|--------|----------|--------|-----------------|
| | | | |
```
---
## 快捷标记符号
- ✅ 已完成 / 已确认
- 🔴 高优先级 / 阻塞问题
- 🟡 中优先级 / 待确认
- 🟢 低优先级 / 信息同步
- ❓ 待讨论 / 需澄清
- 📌 重要决定
- 📎 参考资料链接
- ⏰ 时间敏感
Use when setting up or managing a Turborepo-based monorepo. Covers workspace configuration, task pipelines, caching strategies, shared packages, and CI/CD in...
---
name: monorepo-turborepo
description: Use when setting up or managing a Turborepo-based monorepo. Covers workspace configuration, task pipelines, caching strategies, shared packages, and CI/CD integration for multi-package repositories with Turborepo.
---
# Monorepo with Turborepo
A practical guide to building and managing scalable monorepos using Turborepo.
## When to Use
- Setting up a new monorepo with multiple apps/packages
- Optimizing build/test pipelines with caching
- Sharing UI components, utilities, or configs across apps
- Configuring CI for monorepo with selective builds
## Core Workflow
### 1. Initialize Monorepo
```bash
npx create-turbo@latest my-monorepo
cd my-monorepo
```
**Workspace layout:**
```
my-monorepo/
├── apps/
│ ├── web/ # Next.js app
│ └── docs/ # Docusaurus
├── packages/
│ ├── ui/ # Shared components
│ ├── config/ # Shared ESLint/TS configs
│ └── utils/ # Shared utilities
├── turbo.json
└── package.json
```
### 2. Configure turbo.json Pipeline
```json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
```
### 3. Package.json Root Config
```json
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"test": "turbo test",
"type-check": "turbo type-check",
"clean": "turbo clean && rm -rf node_modules"
},
"devDependencies": {
"turbo": "latest"
}
}
```
### 4. Shared Package Setup (packages/ui)
```json
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.1",
"exports": {
"./*": {
"import": "./src/*.tsx",
"require": "./src/*.tsx"
}
},
"scripts": {
"build": "tsc",
"lint": "eslint src/",
"dev": "tsc --watch"
}
}
```
### 5. Remote Caching (Vercel)
```bash
npx turbo login
npx turbo link
```
Or with custom remote cache:
```bash
turbo build --api="https://your-cache-server.com" --token="$TURBO_TOKEN" --team="your-team"
```
### 6. Selective Builds (Filter)
```bash
# Build only affected packages
turbo build --filter=...[HEAD^1]
# Build specific app and its dependencies
turbo build --filter=web...
# Exclude a package
turbo build --filter=!docs
```
### 7. CI/CD Integration (GitHub Actions)
See `references/ci-github-actions.yml` for a complete workflow.
## Key Principles
- **`^` prefix** in `dependsOn` means "build all dependencies first"
- **`outputs`** defines what gets cached; be explicit
- **`cache: false`** for dev/watch tasks
- **`persistent: true`** for long-running processes
- Always define `exports` in package.json for shared packages
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Cache miss every run | Check `outputs` paths are correct |
| Circular dependency | Use `turbo graph` to visualize |
| Package not found | Verify `workspaces` glob in root package.json |
| Slow cold build | Enable remote caching |
FILE:references/ci-github-actions.yml
# GitHub Actions CI for Turborepo Monorepo
# Place at: .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
env:
TURBO_TOKEN: { secrets.TURBO_TOKEN}
TURBO_TEAM: { secrets.TURBO_TEAM}
jobs:
build:
name: Build & Test
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2 # needed for --filter=[HEAD^1]
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Setup Node.js { matrix.node-version}
uses: actions/setup-node@v4
with:
node-version: { matrix.node-version}
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Type check
run: pnpm turbo type-check
- name: Lint
run: pnpm turbo lint
- name: Test
run: pnpm turbo test --concurrency=4
- name: Build
run: pnpm turbo build
# Selective build for PRs (only affected packages)
affected:
name: Affected Packages Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v3
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Build affected packages only
run: |
pnpm turbo build --filter=...[origin/main]
env:
TURBO_TOKEN: { secrets.TURBO_TOKEN}
TURBO_TEAM: { secrets.TURBO_TEAM}
deploy-preview:
name: Deploy Preview
needs: [build]
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Deploy web to Vercel (preview)
run: |
pnpm turbo build --filter=web
npx vercel --token={ secrets.VERCEL_TOKEN} \
--scope={ secrets.VERCEL_ORG_ID} \
--project={ secrets.VERCEL_PROJECT_ID}
FILE:references/shared-packages-patterns.md
# Shared Packages Patterns in Turborepo
## Package Types
### 1. UI Component Library (packages/ui)
```tsx
// packages/ui/src/button.tsx
import * as React from "react";
export interface ButtonProps {
children: React.ReactNode;
variant?: "primary" | "secondary" | "ghost";
size?: "sm" | "md" | "lg";
onClick?: () => void;
disabled?: boolean;
className?: string;
}
export function Button({
children,
variant = "primary",
size = "md",
onClick,
disabled = false,
className,
}: ButtonProps) {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-variant btn-size className ?? ""`}
>
{children}
</button>
);
}
```
```json
// packages/ui/package.json
{
"name": "@repo/ui",
"version": "0.0.1",
"private": true,
"main": "./src/index.tsx",
"types": "./src/index.tsx",
"exports": {
".": "./src/index.tsx",
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
},
"scripts": {
"lint": "eslint src/ --max-warnings 0",
"type-check": "tsc --noEmit"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
```
### 2. Utility Library (packages/utils)
```ts
// packages/utils/src/format.ts
export function formatDate(date: Date, locale = "zh-CN"): string {
return new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(date);
}
export function formatCurrency(
amount: number,
currency = "CNY",
locale = "zh-CN"
): string {
return new Intl.NumberFormat(locale, {
style: "currency",
currency,
}).format(amount);
}
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^\w-]+/g, "")
.replace(/--+/g, "-")
.trim();
}
```
### 3. Database Package (packages/database)
```ts
// packages/database/src/client.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log:
process.env.NODE_ENV === "development"
? ["query", "error", "warn"]
: ["error"],
});
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}
export * from "@prisma/client";
```
```json
// packages/database/package.json
{
"name": "@repo/database",
"version": "0.0.1",
"private": true,
"main": "./src/client.ts",
"scripts": {
"build": "prisma generate",
"db:push": "prisma db push",
"db:migrate": "prisma migrate dev"
}
}
```
## Consuming Shared Packages in Apps
```tsx
// apps/web/app/page.tsx
import { Button } from "@repo/ui/button";
import { formatDate } from "@repo/utils";
import { prisma } from "@repo/database";
export default async function Page() {
const users = await prisma.user.findMany();
return (
<div>
{users.map((u) => (
<div key={u.id}>
<p>{u.name} — {formatDate(new Date(u.createdAt))}</p>
<Button variant="secondary">View Profile</Button>
</div>
))}
</div>
);
}
```
## Versioning Strategy
| Strategy | When to Use |
|----------|-------------|
| `workspace:*` | Internal packages, always latest |
| Fixed version | External consumers, stable API |
| Changesets | Publishing to npm registry |
```bash
# Using changesets for versioning
npx changeset init
npx changeset add # Create a changeset
npx changeset version # Bump versions
npx changeset publish # Publish to npm
```
FILE:references/workspace-config.md
# Turborepo Workspace Configuration Reference
## Package Manager Setup
### pnpm (Recommended)
```yaml
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
- "tools/*"
```
```json
// package.json
{
"engines": {
"node": ">=18",
"pnpm": ">=8"
},
"packageManager": "[email protected]"
}
```
### npm workspaces
```json
{
"workspaces": ["apps/*", "packages/*"]
}
```
### yarn workspaces
```json
{
"workspaces": {
"packages": ["apps/*", "packages/*"],
"nohoist": ["**/react-native/**"]
}
}
```
---
## Shared Config Packages
### packages/config-typescript/
```json
// package.json
{
"name": "@repo/typescript-config",
"version": "0.0.0",
"private": true,
"files": ["base.json", "nextjs.json", "react-library.json"]
}
```
```json
// base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
}
}
```
```json
// nextjs.json — for Next.js apps
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"plugins": [{ "name": "next" }],
"module": "ESNext",
"jsx": "preserve",
"incremental": true
}
}
```
### packages/config-eslint/
```json
// package.json
{
"name": "@repo/eslint-config",
"version": "0.0.0",
"private": true,
"files": ["base.js", "next.js", "react-internal.js"],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.0.0",
"eslint-config-prettier": "^9.0.0"
}
}
```
```js
// base.js
module.exports = {
extends: ["eslint:recommended", "prettier"],
rules: {
"no-console": "warn"
},
env: {
node: true,
es2022: true
}
};
```
---
## App Configuration (apps/web)
```json
// apps/web/package.json
{
"name": "web",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"lint": "next lint",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@repo/ui": "workspace:*",
"@repo/utils": "workspace:*"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*"
}
}
```
---
## Environment Variables
```bash
# .env.turbo (root level)
TURBO_TOKEN=your_vercel_remote_cache_token
TURBO_TEAM=your_team_slug
TURBO_REMOTE_ONLY=false # set true to only use remote cache
```
## Useful Turbo CLI Flags
```bash
turbo build --dry-run # Preview what would run
turbo build --graph # Output dependency graph
turbo build --concurrency=4 # Limit parallel tasks
turbo build --no-cache # Skip cache reads
turbo build --force # Ignore cache, re-run all
turbo build --summarize # Output run summary JSON
```
Use this skill when planning a podcast episode or audio program. Covers episode structure design, topic research, interview question frameworks, show notes w...
--- name: podcast-outline description: > Use this skill when planning a podcast episode or audio program. Covers episode structure design, topic research, interview question frameworks, show notes writing, and audience engagement strategies for Chinese and bilingual podcasts. --- # 播客/音频节目策划助手 ## 适用场景 - 策划单期播客节目结构与内容 - 设计访谈嘉宾问题框架 - 撰写节目文案(简介/标题/show notes) - 规划播客系列主题与内容日历 - 独立播客或企业品牌播客内容设计 ## 核心工作流 ### Step 1:确定节目定位 ``` 播客定位四要素: 1. 目标听众:[具体描述谁在听,他们的痛点/兴趣] 2. 节目形式:独白/双主播/访谈/圆桌/纪录片 3. 时长定位:15min(通勤型)/ 30min(干货型)/ 60min+(深度型) 4. 更新频率:每周/双周/月更 5. 核心价值主张:听完这个节目,听众会得到______ ``` ### Step 2:单集结构设计 #### 访谈型节目结构(45-60分钟) ``` [0-2min] 片头:品牌音乐 + 主持人自我介绍 [2-5min] 本期预告:今天聊什么,为什么值得听 [5-10min] 嘉宾介绍 + 暖场破冰问题 [10-40min] 核心访谈:3-4个主题模块 [40-50min] 快问快答 / 推荐一本书/一个工具 [50-55min] 嘉宾结语 + 如何联系/关注嘉宾 [55-60min] 主持人总结 + 下期预告 + 订阅引导 ``` #### 独白干货型节目(20-30分钟) ``` [0-1min] Hook:本期最核心的1个洞见/结论 [1-3min] 为什么聊这个话题(背景/痛点) [3-20min] 主体内容:3个要点,每点5-6分钟 [20-25min] 总结 + 行动建议(听众可以马上做什么) [25-30min] 互动引导 + 下期预告 ``` ### Step 3:访谈问题设计框架 #### 开场破冰类(选1-2个) - "用3个词描述你自己?" - "最近让你兴奋的一件事是什么?" - "你通常怎么向陌生人介绍自己的工作?" #### 背景深挖类 - "你是怎么走上[领域]这条路的?有什么关键转折点?" - "哪个经历让你对[话题]的看法发生了180度转变?" - "你在[领域]犯过的最大错误是什么?从中学到什么?" #### 洞见提炼类 - "大多数人对[话题]最大的误解是什么?" - "如果只能给[目标听众]一条建议,你会说什么?" - "5年后,[行业/领域]会有什么不一样?" #### 具体案例类 - "能给我们讲一个具体的案例吗?" - "这个方法在实际中是怎么操作的,有什么细节?" - "你见过最极端的[正面/负面]案例是什么样的?" #### 收尾互动类 - "有什么问题是你希望我问你但没有问到的?" - "听众可以通过哪里继续关注你/了解更多?" - "推荐一本书/一部电影/一个资源?" ### Step 4:节目简介写作公式 ``` 标题格式: A. [嘉宾名] × [主播名]:[核心话题/金句] B. EP.X | [数字/结论]:[具体话题] C. [反常识观点]——对话[嘉宾身份] 简介结构(300-500字): ① 一句话总结本期价值(听众能学到什么) ② 嘉宾背景介绍(2-3句,聚焦与本期话题相关的经历) ③ 本期聊了什么(3-5个要点,用bullet列出) ④ 精彩摘录(1-2句嘉宾金句) ⑤ 时间戳(章节标记) ⑥ 嘉宾社交账号 + 相关资源链接 ``` ### Step 5:Show Notes 模板 ```markdown ## 本期嘉宾 [姓名],[职位/身份] 微博/公众号/网站:[链接] ## 本期聊了什么 - [要点1] - [要点2] - [要点3] ## 时间轴 00:00 片头与介绍 02:30 [话题1] 15:00 [话题2] 28:00 [话题3] 42:00 快问快答 55:00 总结与下期预告 ## 嘉宾推荐 📚 书籍:《[书名]》 🔧 工具:[工具名] 🎙️ 播客:[播客名] ## 节目信息 订阅/关注:[各平台链接] 合作/投稿:[联系方式] ``` ## 播客平台分发清单 | 平台 | 用户群 | 提交要求 | |------|--------|----------| | 小宇宙 | 中文核心播客用户 | RSS订阅 | | 喜马拉雅 | 最大中文音频平台 | 直接上传 | | 网易云音乐 | 年轻用户 | 后台申请 | | Apple Podcasts | 苹果用户 | RSS提交 | | Spotify | 全球+中国 | RSS提交 | | 荔枝FM | 泛娱乐用户 | 直接上传 | FILE:references/content-strategy.md # 播客选题与内容策略 ## 内容策略矩阵 ### 内容类型分布(建议) ``` 常青内容(40%): - 方法论/框架类(长期有价值,不过时) - 入门指南类(持续吸引新听众) - 深度访谈(有时间价值的嘉宾) 热点内容(30%): - 行业新闻解读 - 趋势预测讨论 - 热门话题借势 系列内容(30%): - 主题系列(建立收听习惯) - 听众问答系列 - 复盘/年度总结系列 ``` ## 选题方法 ### 内容挖掘来源 1. **听众反馈**:评论区/私信高频问题 2. **竞品分析**:同类播客高播放量选题 3. **行业动态**:微博热搜/微信公众号热文 4. **书籍延伸**:热门书籍的主题播客化 5. **嘉宾引发**:嘉宾带来的话题拓展 ### 选题评估维度 | 维度 | 权重 | 评分标准 | |------|------|---------| | 目标听众相关性 | 30% | 与核心受众画像匹配度 | | 话题时效性 | 20% | 当下热度 vs 长期价值 | | 内容独特性 | 25% | 与竞品差异化程度 | | 执行可行性 | 25% | 嘉宾资源/资料获取难度 | ## 嘉宾邀约模板 ``` 主题:[播客名]邀请函——对话[话题] [嘉宾姓名]您好: 我是[播客名]的主播[名字]。我们是一档专注于[领域]的播客, 在[平台]已有[数字]位订阅听众。 我们最近在策划关于「[话题]」的系列内容,在研究这个话题时, 多次看到您在[具体事件/文章/项目]中的深刻见解,非常认同 您对于[具体观点]的看法。 因此诚挚邀请您参与我们的节目录制: - 形式:线上音频访谈(腾讯会议/飞书) - 时长:约60分钟 - 聊的方向:[3个核心问题方向] - 预计发布时间:[日期] 如果您有兴趣,我们可以进一步沟通细节。期待您的回复! [签名] [联系方式] [播客链接] ``` ## 录制准备清单 ### 录制前(提前1周) - [ ] 确认嘉宾时间,发送日历邀请 - [ ] 发送节目简介和参考问题清单给嘉宾 - [ ] 准备背景研究材料(嘉宾文章/演讲/采访) - [ ] 测试录音设备和网络连接 ### 录制前(当天) - [ ] 提前30分钟测试连接 - [ ] 准备问题提纲(打印或双屏显示) - [ ] 关闭通知/消息提醒 - [ ] 准备备用录音方案(手机录音) ### 录制后(48小时内) - [ ] 原始音频备份 - [ ] 粗剪(去除长时间停顿/口误) - [ ] 精剪(节奏优化/片头片尾) - [ ] 撰写show notes - [ ] 发送给嘉宾确认 ## 播客成长指标 ### 早期(0-100期)关注: - 完听率 > 60%(质量指标) - 自然订阅增长率 - 评论率和互动质量 ### 成长期关注: - 月活听众数(MAL) - 老听众留存率(>40%为健康) - 跨平台分发效率 - 商业化询问量 FILE:references/production-tech.md # 播客技术与制作参考 ## 录音设备推荐 ### 入门级(预算1000元以内) | 设备 | 价格 | 适用场景 | |------|------|---------| | 铁三角 ATR2100x | ¥400-500 | USB/XLR双接口,居家录音 | | 森海塞尔 MK4 | ¥600-800 | 电容麦,清晰度高 | | 罗技 Blue Snowball | ¥300-400 | 入门USB麦 | ### 进阶级(预算3000元) | 设备 | 价格 | 特点 | |------|------|------| | Shure SM7B | ¥2200-2500 | 业界标准动圈麦 | | Rode PodMic | ¥1200-1500 | 专为播客设计 | | Focusrite Scarlett 2i2 | ¥800 | 声卡(配合XLR麦使用) | ## 录音软件推荐 | 软件 | 平台 | 价格 | 适用场景 | |------|------|------|---------| | Audacity | Win/Mac | 免费 | 入门剪辑 | | GarageBand | Mac | 免费 | Mac用户首选 | | Adobe Audition | Win/Mac | 订阅 | 专业修音 | | Hindenburg Journalist | Win/Mac | 订阅 | 专为播客设计 | | Descript | Win/Mac | 免费+订阅 | AI转录+文字剪辑 | ## 远程录音工具 | 工具 | 特点 | 价格 | |------|------|------| | Riverside.fm | 本地录制,高质量 | $15/月 | | Squadcast | 高质量远程录音 | $20/月 | | Zencastr | 免费版可用 | 免费+付费 | | 腾讯会议+本地录音 | 国内常用 | 免费 | **推荐方案**:主播本地录音 + 嘉宾本地录音(各自保存),后期合并,音质最佳 ## 音频后期处理标准 ### 响度标准(LUFS) ``` 苹果播客建议:-16 LUFS Spotify建议:-14 LUFS 一般推荐:-16 to -14 LUFS 使用工具: - Auphonic(自动化在线处理,免费额度) - Adobe Audition Match Loudness - Logic Pro / Final Cut Pro ``` ### 基础降噪步骤 1. 采样背景噪音(安静段2-3秒) 2. 应用噪音抑制(Audacity: Effect → Noise Reduction) 3. 去除呼吸声和口腔音 4. 压缩动态范围(让音量更均匀) 5. EQ调整(人声通常增强2kHz-4kHz区间) ## RSS托管服务对比 | 服务 | 价格/月 | 特点 | |------|---------|------| | 小宇宙托管 | 免费 | 国内平台,一键分发 | | Anchor(Spotify) | 免费 | 全球分发,数据分析 | | Buzzsprout | $12起 | 专业分析,美国主流 | | Podbean | $9起 | 功能全面 | | 荔枝播客 | 免费 | 国内平台 | ## 播客封面设计规范 ``` 尺寸:3000×3000px(正方形) 格式:JPG/PNG 文件大小:≤500KB(各平台要求) 分辨率:72dpi(显示用) 设计要点: ✅ 在手机小图标(100×100px)上也能辨识 ✅ 字体大,颜色对比强 ✅ 主色调统一,建立视觉识别 ✅ 包含节目名称(英文/中文) ❌ 避免过多文字(小图看不清) ❌ 避免浅色背景(对比度低) ``` ## 发布流程 SOP ``` 录制完成 ↓ 初剪(去掉明显错误/长停顿) ↓ 精剪(节奏优化,控制时长) ↓ 混音(片头/片尾/音乐床) ↓ 响度标准化(Auphonic) ↓ 撰写show notes + 标题 ↓ 上传到托管平台 ↓ 设置发布时间(最佳:周二-周四早8点或晚8点) ↓ 各社交平台预热发布 ↓ 发布当天回复评论,推动算法推荐 ```
Use this skill when helping users plan personal finances, create monthly budgets, track expenses, set savings goals, or optimize spending habits. Covers budg...
--- name: budget-planner-cn description: > Use this skill when helping users plan personal finances, create monthly budgets, track expenses, set savings goals, or optimize spending habits. Covers budgeting frameworks, expense categorization, debt management, and emergency fund planning for Chinese users. --- # 个人理财预算规划助手 ## 适用场景 - 制定月度/年度个人预算计划 - 分析收支结构,找到节流空间 - 设定储蓄目标(买房/旅行/应急金) - 债务管理与还款优先级排序 - 工资涨薪后的资金重新分配 ## 核心工作流 ### Step 1:收集财务现状 收集以下信息(引导用户填写): ``` 月收入(税后):_____ 元 固定支出:房租_____ / 房贷_____ / 车贷_____ 日常开销:餐饮_____ / 交通_____ / 购物_____ 其他:娱乐_____ / 订阅服务_____ / 人情往来_____ 当前存款:_____ 元 负债情况:信用卡_____ / 网贷_____ / 其他_____ ``` ### Step 2:应用预算框架 #### 50/30/20 法则(入门推荐) | 分类 | 比例 | 用途 | |------|------|------| | 必要支出 | 50% | 房租/餐饮/交通/水电 | | 弹性消费 | 30% | 娱乐/购物/社交 | | 储蓄投资 | 20% | 紧急备用金/理财/还债 | #### 六罐子理财法(精细化) | 罐子 | 比例 | 用途 | |------|------|------| | 生活必需 | 55% | 基本生活开销 | | 长期储蓄 | 10% | 大额目标(买房/教育) | | 教育成长 | 10% | 学习/技能提升 | | 财务自由 | 10% | 投资理财 | | 享乐休闲 | 10% | 旅游/娱乐 | | 慈善公益 | 5% | 礼金/公益 | ### Step 3:制定月度预算表 ``` 📊 月度预算模板(月收入:10,000元为例) 【收入】 工资收入:9,500 副业收入:500 ───────────────── 月收入合计:10,000 【必要支出(目标≤5,000)】 房租/房贷:2,500 餐饮伙食:1,200 交通出行:300 水电网费:200 手机话费:100 ───────────────── 必要支出小计:4,300 【弹性消费(目标≤3,000)】 购物消费:800 娱乐休闲:500 社交人情:400 健身美容:300 ───────────────── 弹性消费小计:2,000 【储蓄投资(目标≥2,000)】 紧急备用金:500 定期理财:1,000 投资账户:500 ───────────────── 储蓄合计:2,000 当月结余:1,700(可补充备用金或再投资) ``` ### Step 4:紧急备用金计划 - **目标金额**:3-6个月生活费(最低标准) - **建议存放**:货币基金(余额宝/零钱通)或活期理财 - **到达时间**:建议12个月内建立完整备用金 ``` 备用金规划示例: 月生活开销:5,000元 目标备用金:5,000 × 6 = 30,000元 现有备用金:10,000元 缺口:20,000元 每月补充:1,000元 → 20个月达标 ``` ### Step 5:负债清理策略 #### 雪崩法(省利息,适合理性型) 按利率从高到低排序,先还利率最高的债务 #### 雪球法(有成就感,适合需要激励型) 按余额从小到大排序,先还最小的债务,建立还款信心 ``` 负债优先级示例: 1. 网贷 5,000元 @ 年化18% → 优先还清 2. 信用卡 20,000元 @ 年化15% → 其次 3. 家人借款 10,000元 @ 0% → 最后 ``` ## 输出建议格式 生成用户的个性化预算时,包含: 1. **当前财务诊断**:收支比、储蓄率、负债率 2. **优化建议**:3个可立即执行的改进点 3. **月度预算表**:详细到每个分类的金额 4. **3个月目标**:可量化的短期里程碑 5. **年度目标**:与用户价值观相关的大目标 ## 关键财务健康指标 | 指标 | 健康值 | 警戒值 | |------|--------|--------| | 储蓄率 | ≥20% | <10% | | 负债收入比 | <30% | >50% | | 备用金 | 3-6个月支出 | <1个月 | | 必要支出比 | <50% | >70% | FILE:references/budget-cases.md # 预算规划实战案例与工具 ## 典型用户画像与方案 ### 案例一:应届生(月薪6,000) **现状**: - 月薪6,000(税后) - 租房2,000,餐饮1,200,交通400 - 无存款,无负债 - 目标:1年存够1万元备用金 **推荐方案(50/30/20)**: ``` 必要支出(50% = 3,000) 房租:2,000 餐饮:700(降低外卖频次) 交通:300 弹性消费(30% = 1,800) 娱乐购物:1,000 社交:500 个人成长:300 储蓄(20% = 1,200) 备用金定投:800/月 学习投资:400/月 存满1万需要:10,000 ÷ 800 = 12.5个月 ✅ ``` --- ### 案例二:中产夫妻(家庭月入25,000) **现状**: - 双收入:15,000 + 10,000 = 25,000 - 房贷:6,500/月(贷款20年,剩余15年) - 孩子教育:2,000/月 - 目标:5年内攒够孩子教育金30万 **推荐方案**: ``` 必要支出(50% = 12,500) 房贷:6,500 孩子教育:2,000 餐饮:2,000 交通:800 其他:1,200 弹性消费(25% = 6,250) 购物娱乐:3,000 社交人情:2,000 其他:1,250 储蓄投资(25% = 6,250) 教育金专户:5,000/月(沪深300指数定投) 应急备用金补充:1,250/月 5年教育金测算: 5,000 × 60个月 = 30万本金 + 预期投资收益 ≈ 33-35万 ``` --- ### 案例三:背负债务(月薪8,000) **现状**: - 月薪8,000 - 信用卡欠款:25,000元(年化15%) - 网贷:8,000元(年化18%) - 每月最低还款:1,800元 - 日常支出:4,000元 **债务清理方案(雪崩法)**: ``` 月支出分配: 必要生活:4,000 最低还款(信用卡):750 最低还款(网贷):400 额外还款(雪崩):2,850 应急备用金:200(先建立最低保障) 还款顺序: Step 1:先还清网贷8,000(约3个月) Step 2:集中攻信用卡(剩余还款能力+1,200释放) Step 3:全清后,2,850/月用于储蓄 预计全清时间:15-18个月 节省利息:约8,000元 ``` ## Excel/表格记账模板结构 ``` Sheet 1:月度总览 ┌──────────────┬──────────┬──────────┬──────────┐ │ 分类 │ 预算 │ 实际 │ 差额 │ ├──────────────┼──────────┼──────────┼──────────┤ │ 收入 │ 10,000 │ 10,500 │ +500 │ │ 必要支出 │ 5,000 │ 4,800 │ +200 │ │ 弹性消费 │ 3,000 │ 3,400 │ -400 │ │ 储蓄 │ 2,000 │ 2,300 │ +300 │ └──────────────┴──────────┴──────────┴──────────┘ Sheet 2:明细记录 日期 | 金额 | 分类 | 子分类 | 支付方式 | 备注 Sheet 3:年度目标追踪 目标 | 目标金额 | 已存 | 进度% | 预计达成时间 ``` ## 推荐记账App | App | 特点 | 适合人群 | |-----|------|---------| | 随手记 | 功能全面,支持多账户 | 记账入门用户 | | 钱迹 | 简洁,支持账单导入 | 极简主义用户 | | 挖财 | 支持理财账户联动 | 有投资的用户 | | MoneyWiz | 多平台同步 | 有海外账户 | | Excel自建 | 完全自定义 | 有数据分析需求 | ## 年度财务复盘清单 **每年12月底执行**: - [ ] 计算全年净储蓄金额 - [ ] 复盘各分类实际 vs 预算偏差 - [ ] 检查投资账户收益率 - [ ] 更新净资产(资产-负债) - [ ] 设定下年度3个财务目标 - [ ] 检查保险是否需要更新 - [ ] 评估是否需要调整储蓄率 FILE:references/financial-tools-cn.md # 中国主要理财工具对比参考 ## 活期/短期理财(备用金存放) | 工具 | 年化收益 | 流动性 | 适合场景 | |------|----------|--------|----------| | 余额宝(天弘基金) | 1.5-2.5% | T+0到账 | 日常备用金 | | 零钱通(微信) | 1.5-2.3% | T+0到账 | 微信支付场景 | | 招行朝朝宝 | 2.0-2.8% | T+0到账 | 招行用户 | | 银行活期 | 0.2-0.35% | 即时 | 基础账户 | | 7天通知存款 | 1.5-2.0% | 提前7天通知 | 短期闲置 | ## 中期储蓄工具 | 工具 | 年化收益 | 期限 | 适合场景 | |------|----------|------|----------| | 3年定期存款 | 2.6-3.0% | 3年 | 保守型中期目标 | | 大额存单 | 2.5-3.1% | 1-3年 | 20万起,较高收益 | | 国债(储蓄国债) | 2.35-2.50% | 3/5年 | 稳健,免税 | | 银行理财R2 | 2.5-3.5% | 1-12个月 | 保本偏好 | ## 长期投资工具(财务自由账户) | 工具 | 预期收益 | 风险 | 适合场景 | |------|----------|------|----------| | 沪深300指数基金 | 长期8-10% | 中等 | 定投积累 | | 偏股混合基金 | 长期8-12% | 中高 | 专业管理 | | 个人养老金账户 | 基金/存款 | 低中 | 税收优惠,锁定至退休 | | 黄金ETF | 长期3-5% | 低中 | 资产配置多元化 | ## 个人养老金账户要点(2023起) - 每年最高缴纳:12,000元 - 税收优惠:缴费额可抵税(月薪25,000以上最合算) - 锁定期:至法定退休年龄方可领取 - 投资选择:基金/存款/保险/理财产品 **是否值得开立计算**: ``` 月薪30,000,税率20% 年缴12,000元个人养老金 每年节税:12,000 × 20% = 2,400元 相当于2%的额外收益 ``` ## 信用卡使用建议 ### 正确使用方式 - ✅ 每月全额还款,享受免息期(最长56天) - ✅ 积分换里程/礼品,增加实际收益 - ✅ 利用信用卡账单日优化现金流 ### 避免的行为 - ❌ 只还最低还款额(年化利率约18%) - ❌ 取现(立即计息+手续费) - ❌ 以卡养卡(债务滚雪球) ### 信用卡免息期计算 ``` 账单日:每月5日 还款日:每月25日 如果5日刚过账单日后消费(如6日): 免息期 = 到下次账单日(次月5日)+ 还款期(20天)= 约50天 如果账单日前消费(如4日): 免息期 = 仅到本月还款日(25日)= 约21天 结论:账单日后的第1天消费,享受最长免息期 ``` ## 常见理财误区 | 误区 | 正确做法 | |------|---------| | 存了钱再开始理财 | 每月自动转入,哪怕从100元开始 | | 先把网贷还清再储蓄 | 高息债务与储蓄同步推进,保留基础备用金 | | 追热门基金 | 长期定投宽基指数,不择时 | | 年底一次性结算 | 月度对账,发现问题及时调整 | | 收入高就不用记账 | 无论收入多少,了解流向才能优化 |
Use this skill when working with Prisma ORM in Node.js/TypeScript projects. Covers schema design, migrations, query optimization, relations, transactions, an...
---
name: prisma-patterns
description: >
Use this skill when working with Prisma ORM in Node.js/TypeScript projects.
Covers schema design, migrations, query optimization, relations, transactions,
and best practices for production-ready database interactions with Prisma 5+.
---
# Prisma ORM Patterns
## When to Use
- Designing or migrating a Prisma schema
- Writing complex queries with relations, filtering, or pagination
- Handling transactions and error scenarios
- Optimizing N+1 queries and performance
- Setting up Prisma in monorepos or serverless environments
## Core Workflow
### 1. Schema Design Principles
```prisma
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
profile Profile?
@@index([email])
@@map("users")
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
tags Tag[] @relation("PostTags")
@@index([authorId, published])
@@map("posts")
}
```
### 2. Migration Workflow
```bash
# Development: auto-apply
npx prisma migrate dev --name add_user_profile
# Production: generate SQL only, review, then deploy
npx prisma migrate deploy
# Reset dev database
npx prisma migrate reset
# Introspect existing DB
npx prisma db pull
```
### 3. Client Initialization (Singleton Pattern)
```typescript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
})
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
```
### 4. Common Query Patterns
```typescript
// Paginated query with relations
const getPostsPage = async (page: number, limit = 10) => {
const [posts, total] = await prisma.$transaction([
prisma.post.findMany({
where: { published: true },
include: {
author: { select: { id: true, name: true } },
_count: { select: { tags: true } },
},
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit,
}),
prisma.post.count({ where: { published: true } }),
])
return { posts, total, pages: Math.ceil(total / limit) }
}
// Upsert pattern
const upsertUser = async (email: string, name: string) => {
return prisma.user.upsert({
where: { email },
update: { name },
create: { email, name },
})
}
// Avoid N+1: use include vs separate queries
const postsWithAuthors = await prisma.post.findMany({
include: { author: true }, // Single JOIN query, not N+1
})
```
### 5. Transactions
```typescript
// Interactive transaction (recommended for complex logic)
const transferCredits = async (fromId: string, toId: string, amount: number) => {
return prisma.$transaction(async (tx) => {
const from = await tx.user.findUniqueOrThrow({ where: { id: fromId } })
if (from.credits < amount) throw new Error('Insufficient credits')
await tx.user.update({
where: { id: fromId },
data: { credits: { decrement: amount } },
})
await tx.user.update({
where: { id: toId },
data: { credits: { increment: amount } },
})
})
}
```
### 6. Error Handling
```typescript
import { Prisma } from '@prisma/client'
const safeCreate = async (data: Prisma.UserCreateInput) => {
try {
return await prisma.user.create({ data })
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2002') {
throw new Error(`Unique constraint violated: e.meta?.target`)
}
}
throw e
}
}
```
## Best Practices
- Always use `select` to limit returned fields in production queries
- Add `@@index` for frequently filtered/sorted columns
- Use `findUniqueOrThrow` / `findFirstOrThrow` to avoid null checks
- Prefer `$transaction` for multi-step operations
- Enable query logging in development only
- Use connection pooling (PgBouncer / Prisma Accelerate) in serverless
FILE:references/query-optimization.md
# Prisma Query Optimization Patterns
## Select vs Include
```typescript
// ❌ Over-fetching: returns all fields including large ones
const users = await prisma.user.findMany({
include: { posts: true }
})
// ✅ Only fetch what you need
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
posts: {
select: { id: true, title: true },
where: { published: true },
take: 5,
}
}
})
```
## Cursor-Based Pagination (better than offset for large datasets)
```typescript
const getCursorPage = async (cursor?: string, limit = 20) => {
const posts = await prisma.post.findMany({
take: limit + 1, // Fetch one extra to know if there's a next page
...(cursor && {
cursor: { id: cursor },
skip: 1,
}),
orderBy: { createdAt: 'desc' },
select: { id: true, title: true, createdAt: true },
})
const hasNextPage = posts.length > limit
const items = hasNextPage ? posts.slice(0, -1) : posts
const nextCursor = hasNextPage ? items[items.length - 1].id : null
return { items, nextCursor, hasNextPage }
}
```
## Batch Operations
```typescript
// Create many (faster than loop)
await prisma.tag.createMany({
data: [
{ name: 'typescript', slug: 'typescript' },
{ name: 'prisma', slug: 'prisma' },
{ name: 'nextjs', slug: 'nextjs' },
],
skipDuplicates: true,
})
// Update many with same value
await prisma.post.updateMany({
where: { authorId: userId, published: false },
data: { published: true },
})
// Delete many
await prisma.post.deleteMany({
where: {
createdAt: { lt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) },
published: false,
},
})
```
## Raw Queries (when Prisma can't express it)
```typescript
// Raw SQL with type safety
const result = await prisma.$queryRaw<{ count: bigint }[]>`
SELECT COUNT(*) as count
FROM users
WHERE created_at > new Date('2025-01-01')
AND metadata->>'plan' = 'pro'
`
// Execute raw (no return value)
await prisma.$executeRaw`
UPDATE posts SET view_count = view_count + 1
WHERE id = postId
`
```
## Aggregations
```typescript
const stats = await prisma.post.aggregate({
_count: { id: true },
_avg: { viewCount: true },
_max: { createdAt: true },
where: { published: true },
})
// Group by
const tagCounts = await prisma.post.groupBy({
by: ['status'],
_count: { id: true },
orderBy: { _count: { id: 'desc' } },
})
```
## Performance Tips
1. **Index strategy**: Add `@@index` for any field used in `where`, `orderBy`, or `JOIN`
2. **Limit depth**: Avoid nesting includes more than 2 levels deep
3. **Use `select` in production**: Never return full objects when partial is enough
4. **Connection pooling**: Set `connection_limit` in DATABASE_URL; use PgBouncer for serverless
5. **Avoid `findMany` without limits**: Always add `take` to prevent runaway queries
6. **Monitor slow queries**: Enable `log: ['query']` in dev; use `EXPLAIN ANALYZE` for slow ones
## Serverless Considerations
```typescript
// PrismaClient in serverless (avoid cold start connection exhaustion)
// Option 1: Prisma Accelerate (recommended)
// DATABASE_URL="prisma://accelerate.prisma-data.net/?api_key=..."
// Option 2: Manual connection limit
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL + '?connection_limit=1&pool_timeout=20',
},
},
})
// Always disconnect in serverless handlers if not using Accelerate
export const handler = async (event: any) => {
try {
const result = await prisma.user.findMany()
return result
} finally {
await prisma.$disconnect()
}
}
```
FILE:references/schema-design.md
# Prisma Schema Design Reference
## Field Types & Modifiers
```prisma
model Example {
// ID strategies
id Int @id @default(autoincrement())
id String @id @default(cuid()) // Collision-resistant
id String @id @default(uuid()) // UUID v4
id Bytes @id @default(dbgenerated("gen_random_uuid()"))
// Common field patterns
email String @unique
slug String @unique @db.VarChar(100)
bio String? @db.Text // Nullable large text
metadata Json? // Flexible JSON storage
score Float @default(0.0)
status Status @default(ACTIVE) // Enum
// Timestamps
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime? // Soft delete
// Composite unique
@@unique([userId, postId])
// Multi-column index
@@index([status, createdAt(sort: Desc)])
}
enum Status {
ACTIVE
INACTIVE
BANNED
}
```
## Relation Patterns
```prisma
// One-to-One
model User {
id String @id @default(cuid())
profile Profile?
}
model Profile {
id String @id @default(cuid())
userId String @unique
user User @relation(fields: [userId], references: [id])
}
// One-to-Many
model Author {
id String @id @default(cuid())
posts Post[]
}
model Post {
id String @id @default(cuid())
authorId String
author Author @relation(fields: [authorId], references: [id])
}
// Many-to-Many (implicit)
model Post {
id String @id @default(cuid())
tags Tag[]
}
model Tag {
id String @id @default(cuid())
posts Post[]
}
// Many-to-Many (explicit — recommended when join table has extra fields)
model PostTag {
postId String
tagId String
createdAt DateTime @default(now())
assignedBy String
post Post @relation(fields: [postId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@id([postId, tagId])
}
```
## Soft Delete Pattern
```prisma
model Post {
id String @id @default(cuid())
deletedAt DateTime?
}
```
```typescript
// Soft delete middleware
prisma.$use(async (params, next) => {
if (params.model === 'Post') {
if (params.action === 'delete') {
params.action = 'update'
params.args.data = { deletedAt: new Date() }
}
if (params.action === 'findMany' || params.action === 'findFirst') {
params.args.where = {
...params.args.where,
deletedAt: null,
}
}
}
return next(params)
})
```
## Connection String Formats
```
# PostgreSQL
postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public&connection_limit=5
# MySQL
mysql://USER:PASSWORD@HOST:PORT/DATABASE
# SQLite (dev only)
file:./dev.db
# With SSL (production)
postgresql://USER:PASSWORD@HOST/DATABASE?sslmode=require
```
## Common Prisma Error Codes
| Code | Meaning | Fix |
|-------|----------------------------------|----------------------------------|
| P2002 | Unique constraint violation | Check for duplicate values |
| P2003 | Foreign key constraint failed | Ensure related record exists |
| P2025 | Record not found | Use findFirst instead of throw |
| P2014 | Relation violation | Check cascade settings |
| P1001 | Can't reach database server | Check DATABASE_URL & network |
FILE:references/testing-migrations.md
# Prisma Testing & Migrations Reference
## Testing with Prisma
### Setup: Test Database Isolation
```typescript
// test/setup.ts
import { execSync } from 'child_process'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
beforeAll(async () => {
// Use a separate test database
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL
execSync('npx prisma migrate deploy', { stdio: 'inherit' })
})
afterEach(async () => {
// Clean tables in dependency order
const tableNames = ['post', 'profile', 'user']
for (const table of tableNames) {
await prisma.$executeRawUnsafe(`DELETE FROM "table"`)
}
})
afterAll(async () => {
await prisma.$disconnect()
})
```
### Factory Pattern for Test Data
```typescript
// test/factories/user.factory.ts
import { prisma } from '@/lib/prisma'
import { Prisma } from '@prisma/client'
let emailCounter = 0
export const createUser = async (
overrides: Partial<Prisma.UserCreateInput> = {}
) => {
return prisma.user.create({
data: {
email: `user-++emailCounter@test.com`,
name: 'Test User',
...overrides,
},
})
}
export const createPost = async (
authorId: string,
overrides: Partial<Prisma.PostCreateInput> = {}
) => {
return prisma.post.create({
data: {
title: 'Test Post',
published: true,
author: { connect: { id: authorId } },
...overrides,
},
})
}
```
### Mocking Prisma (unit tests)
```typescript
// Use jest-mock-extended
import { mockDeep, DeepMockProxy } from 'jest-mock-extended'
import { PrismaClient } from '@prisma/client'
jest.mock('@/lib/prisma', () => ({
prisma: mockDeep<PrismaClient>(),
}))
const prismaMock = prisma as DeepMockProxy<PrismaClient>
test('creates user', async () => {
prismaMock.user.create.mockResolvedValue({
id: 'cuid1',
email: '[email protected]',
name: 'Test',
createdAt: new Date(),
updatedAt: new Date(),
})
const user = await createUser({ email: '[email protected]' })
expect(user.email).toBe('[email protected]')
})
```
## Migration Best Practices
### Naming Conventions
```bash
# Good: descriptive names
npx prisma migrate dev --name add_user_roles
npx prisma migrate dev --name create_audit_log_table
npx prisma migrate dev --name add_post_view_count
# Bad: vague names
npx prisma migrate dev --name update
npx prisma migrate dev --name fix
```
### Custom Migration SQL
```sql
-- migrations/20250101_add_full_text_search/migration.sql
-- Add after Prisma-generated SQL
-- Create GIN index for full-text search (PostgreSQL)
CREATE INDEX posts_content_search_idx ON posts
USING GIN (to_tsvector('english', title || ' ' || COALESCE(content, '')));
```
### Zero-Downtime Migration Checklist
1. **Add nullable column** → deploy app → backfill data → add NOT NULL constraint
2. **Rename column** → add new column → dual-write → migrate reads → drop old
3. **Add index** → use `CREATE INDEX CONCURRENTLY` (PostgreSQL) to avoid locks
4. **Drop column** → remove from code first → deploy → then run migration
```prisma
// Step 1: Make nullable (safe)
model User {
newField String? // Nullable first
}
// Step 2: After backfill (separate migration)
model User {
newField String // Then make required
}
```
## Useful CLI Commands
```bash
# View migration status
npx prisma migrate status
# Generate Prisma Client without migrating
npx prisma generate
# Open Prisma Studio (GUI)
npx prisma studio
# Format schema file
npx prisma format
# Validate schema without migrating
npx prisma validate
# Seed database
npx prisma db seed
# Push schema without migration history (prototyping only)
npx prisma db push
```
## package.json Scripts
```json
{
"scripts": {
"db:migrate": "prisma migrate deploy",
"db:migrate:dev": "prisma migrate dev",
"db:reset": "prisma migrate reset",
"db:seed": "prisma db seed",
"db:studio": "prisma studio",
"db:generate": "prisma generate",
"postinstall": "prisma generate"
},
"prisma": {
"seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts"
}
}
```
Use when coaching English speaking or writing skills: correcting grammar, improving sentence fluency, expanding vocabulary, practicing conversations, prepari...
---
name: language-tutor-en
description: >
Use when coaching English speaking or writing skills: correcting grammar,
improving sentence fluency, expanding vocabulary, practicing conversations,
preparing for IELTS/TOEFL writing, polishing emails or essays, and building
daily English habits. Best for Chinese learners targeting professional English.
---
# English Speaking & Writing Coach
## When to Use
- Grammar correction with explanations (not just fixes)
- Rewriting Chinese-English (Chinglish) into natural English
- Email / essay / report polishing for professional settings
- IELTS / TOEFL writing task coaching
- Conversation practice (job interview, meeting, small talk)
- Vocabulary building for specific domains (tech, business, academic)
## Core Workflow
### 1. Diagnose Before Fixing
When given a piece of writing, identify issues in layers:
1. **Grammar errors** – subject-verb agreement, tense, articles (a/an/the), prepositions
2. **Chinglish patterns** – literal translation that sounds unnatural
3. **Fluency** – choppy sentences, repetitive words, weak transitions
4. **Tone** – too casual / too formal for the context
5. **Structure** – missing topic sentence, unclear logic flow
### 2. Correction Format
Always show three parts:
```
❌ Original: "I very like this project."
✅ Corrected: "I really enjoy working on this project."
💡 Why: "Very" doesn't modify verbs; use "really/greatly/deeply".
"Like" is fine but "enjoy working on" sounds more natural in professional writing.
```
### 3. Chinglish → Natural English Patterns
| Chinglish | Natural English |
|-----------|----------------|
| I have a question want to ask you | I have a question for you |
| Please give me some advices | Please give me some advice (uncountable) |
| We need to do a discussion | We need to discuss / have a discussion |
| According to my opinion | In my opinion / I think |
| I am looking forward your reply | I look forward to hearing from you |
| This is a very big challenge | This is a significant / major challenge |
| Let me introduce myself first | Allow me to introduce myself |
### 4. Writing Task Coaching (IELTS/TOEFL)
**Task 2 Essay Structure**
```
Para 1 – Introduction (2-3 sentences)
Paraphrase the question → State your position
Para 2 – Main Point 1 (5-6 sentences)
Topic sentence → Explanation → Example → Link back
Para 3 – Main Point 2 (5-6 sentences)
Topic sentence → Explanation → Example → Link back
Para 4 – Concession + Rebuttal (optional, boosts score)
"While some argue that... , I believe..."
Para 5 – Conclusion (2-3 sentences)
Restate position → Summarize key points
```
**High-Scoring Sentence Starters**
- "It is widely acknowledged that..."
- "A growing body of evidence suggests..."
- "This is particularly evident in..."
- "Opponents of this view contend that..."
- "Ultimately, the benefits of X far outweigh..."
### 5. Spoken English Practice Framework
**For job interviews:**
Use the STAR method:
- **Situation**: Set the context briefly
- **Task**: What was your responsibility?
- **Action**: What did YOU do? (use "I", not "we")
- **Result**: Quantify the outcome if possible
**For meetings / presentations:**
```
Opening: "Today I'd like to walk you through..."
Linking: "Building on that point..." / "This brings me to..."
Checking: "Does that make sense?" / "Any questions so far?"
Closing: "To summarize..." / "The key takeaway is..."
```
### 6. Vocabulary Building System
1. **Learn in context**: Don't memorize lists; learn words in sentences
2. **Use spaced repetition**: Review on day 1, 3, 7, 14, 30
3. **Active recall**: Cover the word, recall meaning + example sentence
4. **Domain clusters**: Group by topic (finance, tech, health)
5. **Daily output**: Use 3 new words in writing/speaking each day
### 7. Quick Feedback Checklist
- [ ] Articles used correctly (a/an/the/zero)?
- [ ] Verb tenses consistent?
- [ ] Sentences vary in length and structure?
- [ ] No literal Chinese-to-English translations?
- [ ] Appropriate formal/informal register?
- [ ] Transitions connect paragraphs logically?
FILE:references/common-mistakes.md
# Common Chinese Learner Mistakes & Corrections
## Category 1: Articles (a / an / the)
The most common pain point for Chinese learners.
### Rules Reminder
- **a/an**: First mention, non-specific, countable singular
- **the**: Specific, already mentioned, unique things
- **zero article**: Uncountable, plural general statements, proper nouns
### Error Examples
```
❌ I went to hospital yesterday.
✅ I went to the hospital yesterday.
(specific building → the)
❌ She is best student in class.
✅ She is the best student in the class.
(superlative → the)
❌ The life is short.
✅ Life is short.
(general abstract → no article)
❌ I have a useful information.
✅ I have useful information.
(information is uncountable)
❌ I am engineer.
✅ I am an engineer.
(profession → a/an)
```
---
## Category 2: Verb Tenses
### Simple Past vs. Present Perfect
```
❌ I already finish the report.
✅ I have already finished the report.
(present relevance → present perfect)
❌ Have you seen the news yesterday?
✅ Did you see the news yesterday?
(specific past time → simple past)
```
### Progressive Tense Overuse
```
❌ I am knowing the answer.
✅ I know the answer.
(stative verbs: know/believe/want/love → no progressive)
```
---
## Category 3: Prepositions
| Chinese Concept | Wrong | Correct |
|----------------|-------|---------|
| 在周末 | in weekend | on the weekend |
| 在早上 | in the morning | in the morning ✓ |
| 在周一 | in Monday | on Monday |
| 准时 | on time / in time | on time (punctual) |
| 讨论关于 | discuss about | discuss (no "about") |
| 说明关于 | explain about | explain |
| 结婚于 | marry with | marry / get married to |
| 依靠某人 | rely to someone | rely on someone |
---
## Category 4: Chinglish Phrases
### Very + Adjective Overuse
```
❌ very big / very good / very bad / very important
✅ enormous / excellent / terrible / crucial
significant / outstanding / dreadful / vital
```
### Literal Translations
```
❌ My English is not very good, please forgive me.
✅ Please bear with me, English isn't my first language.
❌ Long time no see! (okay in casual) → formal: It's been a while!
✅ It's great to see you again. / It's been ages!
❌ Can you speak slowly?
✅ Could you please speak more slowly? / Could you slow down a bit?
❌ I want to improve my English level.
✅ I want to improve my English. / I'm working on my English skills.
❌ This question is very have difficulty.
✅ This question is quite challenging.
```
---
## Category 5: Formal vs. Informal Register
### Business Email (too casual → professional)
```
❌ Hey! I need your help ASAP.
✅ Dear [Name], I hope this finds you well. I'd appreciate your assistance with...
❌ I don't know how to do this thing.
✅ I'm uncertain about the best approach for...
❌ Please send me the file right now.
✅ Could you please send me the file at your earliest convenience?
```
### Presentation (too stiff → natural professional)
```
❌ I will now proceed to explain the second point.
✅ Let me move on to the second point.
❌ In accordance with the aforementioned data...
✅ Based on the data we've seen...
```
---
## Category 6: Countable vs. Uncountable Nouns
Common uncountable nouns Chinese learners often pluralize:
- advice (not advices) → a piece of advice
- information (not informations) → a piece of information
- feedback (not feedbacks)
- equipment (not equipments)
- furniture (not furnitures)
- progress (not progresses)
- research (not researches)
- knowledge (not knowledges)
- work (not works) → a piece of work / pieces of work
FILE:references/ielts-toefl-writing.md
# IELTS / TOEFL Writing Reference
## IELTS Writing Task 2 — Band Descriptors Summary
### Band 7 Requirements
- **Task Achievement**: Addresses all parts, clear position throughout
- **Coherence**: Logical progression, good use of cohesive devices
- **Lexical Resource**: Wide vocabulary range, some errors
- **Grammar**: Mix of simple and complex structures, mostly accurate
### Band 8+ Differentiators
- Fully developed ideas with relevant examples
- Varied and precise vocabulary (no repetition)
- Wide range of complex structures used naturally
- Rare errors that don't impede communication
---
## High-Frequency Essay Topics & Vocabulary
### Education
- compulsory education / tuition fees / academic performance
- holistic development / rote learning / critical thinking
- extracurricular activities / vocational training
### Technology
- digital literacy / artificial intelligence / automation
- screen addiction / data privacy / cybersecurity
- remote work / e-commerce / disruptive innovation
### Environment
- carbon footprint / renewable energy / fossil fuels
- biodiversity loss / sustainable development / greenhouse gases
- climate change / deforestation / circular economy
### Society & Culture
- urbanization / social mobility / income inequality
- multiculturalism / globalization / cultural homogenization
- aging population / nuclear family / work-life balance
---
## Cohesive Devices Bank
### Adding Ideas
- Furthermore, / Moreover, / In addition, / Additionally,
- Not only... but also... / As well as this,
### Contrasting
- However, / Nevertheless, / On the other hand,
- While / Although / Despite the fact that
- In contrast, / Conversely,
### Cause & Effect
- As a result, / Consequently, / Therefore, / Thus,
- This leads to / This results in / Due to / Owing to
### Exemplifying
- For instance, / For example, / To illustrate,
- A case in point is / This is evident in
- Such as / Including
### Concluding
- In conclusion, / To summarize, / Overall,
- It is clear that / The evidence suggests that
- Taking everything into account,
---
## TOEFL Integrated Writing Structure
### Task (150-225 words)
Reading argues: [3 main points]
Lecture challenges each point:
**Para 1** – Introduction
"The reading passage claims [X]. However, the professor argues [Y]."
**Para 2** – Counter-point 1
"First, while the reading states [A], the professor contends [B] because [reason]."
**Para 3** – Counter-point 2
"Second, the reading argues [C]; in contrast, the lecture suggests [D]."
**Para 4** – Counter-point 3
"Finally, the reading mentions [E], but the professor refutes this by [F]."
---
## 30 Sentence Starters for Academic Writing
1. It is widely acknowledged that...
2. There is growing evidence to suggest...
3. Proponents of this view argue that...
4. Critics, however, contend that...
5. A significant factor contributing to... is...
6. This phenomenon can be attributed to...
7. The implications of this are far-reaching...
8. One cannot overlook the fact that...
9. A closer examination reveals that...
10. It would be short-sighted to assume that...
11. The benefits of X are manifold...
12. The drawbacks, however, should not be underestimated...
13. Striking a balance between X and Y is essential...
14. While X has merits, it is Y that proves more effective...
15. The long-term consequences of... remain unclear...
FILE:references/speaking-practice.md
# English Speaking Practice Scripts
## Job Interview Practice
### Self-Introduction Template (60-90 seconds)
```
"Good [morning/afternoon]. My name is [Name], and I'm excited to be here today.
I have [X] years of experience in [field/industry], with a focus on [specialty].
In my current role at [Company], I [key achievement using numbers if possible].
Before that, I [brief background — 1-2 sentences].
I'm particularly drawn to this opportunity because [specific reason about the company/role].
I believe my background in [relevant skill] would allow me to [how you'd contribute].
I'm looking forward to discussing how I can bring value to your team."
```
### STAR Answer Template
**Q: Tell me about a time you faced a difficult challenge at work.**
```
S (Situation):
"In my previous role at [Company], we were facing [specific challenge]..."
T (Task):
"My responsibility was to [specific task/goal]..."
A (Action):
"I [specific actions you took — use I not we]:
First, I...
Then, I...
Finally, I..."
R (Result):
"As a result, [specific measurable outcome].
The project [was delivered on time / exceeded targets by X% / etc.]"
```
---
## Business Meeting Phrases
### Starting a Meeting
- "Let's get started, shall we?"
- "Thanks everyone for joining. Let's dive right in."
- "We have [X] items on the agenda today."
### Expressing Opinions
- "From my perspective, I think..."
- "I'd like to build on what [Name] said..."
- "My take on this is..."
- "I see your point, but I'd argue that..."
- "If I may add something here..."
### Asking for Clarification
- "Could you elaborate on that?"
- "Sorry, I didn't quite catch that — could you repeat?"
- "Just to make sure I understand, you're saying that...?"
- "What do you mean by [term]?"
### Presenting Data
- "As you can see from this chart..."
- "The data shows a significant increase in..."
- "This figure represents [X%] of..."
- "Compared to last quarter, we've seen..."
### Wrapping Up
- "So to summarize the key points..."
- "The next steps are..."
- "Let's schedule a follow-up for..."
- "Any final questions before we close?"
---
## Small Talk Topics & Phrases
### Safe Topics
- Weather: "Can you believe this weather lately?"
- Weekend: "Do you have any plans for the weekend?"
- Travel: "Have you traveled anywhere interesting recently?"
- Work: "How's the project going?"
### Responding to Small Talk
- Showing interest: "Oh really? Tell me more about that."
- Agreeing: "Absolutely / I totally agree / That's so true."
- Transitioning: "That reminds me..." / "By the way..."
### Ending a Conversation Politely
- "It was great chatting with you!"
- "I should let you get back to work."
- "Let's catch up again soon."
- "I'll let you go — talk soon!"
---
## Pronunciation Tips for Chinese Speakers
### Common Problem Sounds
**Final consonants** (Chinese syllables are open; English closes)
- li**ve** → don't drop the /v/
- fac**t** → don't drop the /t/
- de**sk** → pronounce the /sk/
**th sounds**
- /θ/ (thin, think, three) — tongue between teeth, blow air
- /ð/ (this, that, the) — tongue between teeth, voice
**r vs. l**
- rice vs. lice
- Practice: tongue up for /l/ (tip touches roof), back for /r/
**Vowel length**
- ship /ɪ/ (short) vs. sheep /iː/ (long)
- full /ʊ/ (short) vs. fool /uː/ (long)
---
## 30-Day Speaking Habit Builder
| Week | Daily Practice (15-20 min) |
|------|---------------------------|
| 1 | Shadowing: copy native speaker audio sentence by sentence |
| 2 | Describe your day in English (voice memo to yourself) |
| 3 | Watch 1 TED Talk, summarize in 3 sentences |
| 4 | Practice 1 STAR story out loud, refine each day |
Use when writing or polishing Chinese business emails: drafting from scratch, adjusting tone (formal/casual/assertive), structuring requests, follow-ups, apo...
--- name: email-writer-cn description: > Use when writing or polishing Chinese business emails: drafting from scratch, adjusting tone (formal/casual/assertive), structuring requests, follow-ups, apologies, proposals, and internal communications. Optimized for Mandarin professional context with proper etiquette. --- # 中文商务邮件写作助手 ## 适用场景 - 从零起草商务邮件(请求、通知、跟进、道歉、合作提案) - 润色已有邮件(改语气、精简内容、纠正表达) - 内部沟通邮件(跨部门协作、汇报、会议通知) - 对外邮件(客户、供应商、合作伙伴、政府机构) ## 核心工作流 ### 第一步:信息收集 使用前需确认: 1. **目标读者**:内部/外部?级别(平级/上级/客户/政府)? 2. **邮件目的**:告知 / 请求 / 道歉 / 跟进 / 提案? 3. **语气要求**:正式 / 专业友好 / 简洁直接? 4. **核心内容**:关键事实/数字/时间节点是什么? 5. **期望行动**:读者需要做什么?截止时间? ### 第二步:结构框架 ``` 主题行(Subject) ↓ 称呼语(Greeting) ↓ 开场白(Context / Purpose) ↓ 正文(Body: 背景 → 要点 → 细节) ↓ 行动呼吁(Call to Action) ↓ 礼貌结语(Closing) ↓ 签名(Signature) ``` ### 第三步:常用模板 **请求类** ``` 主题:关于[事项]的请求/申请 尊敬的[姓名/职位]: 您好! [背景:一句话说明来意] [请求内容:具体说明请求什么,为什么需要,时间要求] 如有需要,我可以[提供额外支持]。 烦请在[时间]前告知,感谢您的支持与配合! 此致 敬礼 [姓名] [职位 | 部门 | 联系方式] ``` **跟进类** ``` 主题:跟进:[原邮件主题] [称呼],您好: [上次沟通时间],我曾就[事项]与您联系。 目前[进展/状态],想再次确认[具体问题],以便我们[下一步推进]。 如您方便,请在[时间]前回复,非常感谢! [结语+签名] ``` **道歉类** ``` 主题:致歉:关于[事项] [称呼],您好: 首先,对于[问题/失误]给您带来的不便,我深表歉意。 [客观说明原因,不推脱] 我们已采取以下措施: 1. [补救措施1] 2. [补救措施2] 我们将确保此类情况不再发生。再次为此向您诚挚道歉。 [结语+签名] ``` ### 第四步:语气调整指南 | 场景 | 建议用词 | |------|---------| | 对上级/客户 | 敬请、烦请、不胜感激、恳请指正 | | 平级合作 | 麻烦、请、感谢配合、期待合作 | | 催促但不失礼 | 如您方便、期望在XX前、如有进展请随时告知 | | 拒绝请求 | 非常感谢您的信任,由于...,暂时无法...,建议... | ### 第五步:质量检查清单 - [ ] 主题行是否清晰说明邮件目的(5-15字) - [ ] 称呼是否正确(姓名/职位拼写无误) - [ ] 一封邮件只聚焦一个核心请求 - [ ] 关键信息是否加粗/分点(避免大段文字) - [ ] 行动呼吁是否明确(谁、做什么、什么时间) - [ ] 语气是否符合关系和场景 - [ ] 是否有错别字/语法问题 FILE:references/business-phrases.md # 商务邮件用语指南 ## 一、开头用语 ### 正式场合(客户、政府、高层) - 尊敬的[姓名/职位]: - 您好!冒昧打扰,敬请谅解。 - 感谢您抽出宝贵时间阅读此邮件。 - 值此[节日/时机]之际,谨向您致以诚挚问候。 ### 专业友好(合作伙伴、平级) - [姓名],您好: - 感谢您的快速回复! - 希望您一切顺利。 - 很高兴再次与您联系。 ### 内部(同事、下属) - [名字]好: - 大家好:(群发) - 各位同学:(团队邮件) --- ## 二、正文过渡用语 ### 引出主题 - 就[事项],特此联系您。 - 写此邮件是为了…… - 根据我们上次的沟通,…… - 关于[日期]讨论的[事项],进展如下: ### 分点说明 - 具体情况如下: - 主要包含以下几点: - 请参阅以下信息: - 特别说明以下事项: ### 附件说明 - 附件为[文件名称],请查阅。 - 详情请参见附件。 - 如附件所示,[概述内容]。 --- ## 三、请求与催促用语 ### 礼貌请求 - 烦请……(最常用,礼貌) - 敬请……(更正式) - 麻烦您……(平级友好) - 能否请您……? - 如方便,请…… ### 设定截止时间 - 烦请于[日期]前回复。 - 如能在[时间]前确认,将不胜感激。 - 由于时间较紧,期望在[日期]收到您的反馈。 ### 温和催促 - 就[事项],想再次确认进展,以便我们及时推进。 - 之前邮件如有遗漏,烦请再次查阅,谢谢。 - 期望尽快得到您的回复,以免影响整体进度。 --- ## 四、拒绝与婉拒用语 ### 婉拒合作/请求 - 非常感谢您的信任,遗憾的是目前……暂无法满足。 - 经过慎重考虑,我们认为目前时机尚不成熟。 - 很抱歉,受限于[原因],我们暂时无法…… ### 建议替代方案 - 我们建议可以考虑……作为替代方案。 - 如贵方仍有需要,我们可以…… - 或许可以将时间调整至[日期]? --- ## 五、结尾用语 ### 期待回复 - 期待您的回复,感谢! - 如有疑问,欢迎随时联系。 - 期待与您进一步沟通。 ### 正式结语 - 此致 敬礼(正式,单独成行) - 顺颂商祺(商业信函) - 谨致问候 ### 友好结语 - 感谢您的支持与配合! - 祝工作顺利! - 祝一切顺心! --- ## 六、常见错误避免 | ❌ 错误写法 | ✅ 正确写法 | 说明 | |------------|------------|------| | 您辛苦了 | 感谢您的协助 | 过度表达 | | 请您务必 | 烦请您 | 语气过于强硬 | | 如有任何不懂 | 如有任何疑问 | "不懂"不礼貌 | | 速回 | 烦请尽快回复 | 太生硬 | | 谢谢谢谢 | 非常感谢 | 重复显得不专业 | | 望悉 | (省略)| 文言文,现代邮件少用 | FILE:references/email-templates.md # 中文商务邮件模板库 ## 一、会议相关 ### 会议邀请 ``` 主题:邀请参加[会议名称] — [日期] [时间] [称呼],您好: 我们计划于[日期][时间]召开[会议主题],会议时长约[X]小时,采用[线上/线下]形式。 会议议程: 1. [议题1](约X分钟) 2. [议题2](约X分钟) 3. 开放讨论 会议链接/地点:[链接/地址] 烦请于[日期]前确认是否参会,如有问题欢迎随时联系。 期待与您的交流! [签名] ``` ### 会议纪要发送 ``` 主题:会议纪要 — [会议名称] [日期] 各位好: 以下为[日期]会议纪要,请查阅并确认行动项。 【会议纪要】 时间:[日期时间] 参会人:[姓名列表] 主要讨论内容: 1. [讨论点1]:[结论/决定] 2. [讨论点2]:[结论/决定] 【行动项】 | 负责人 | 任务 | 截止时间 | |--------|------|---------| | [姓名] | [任务] | [日期] | 如有补充或异议,请在[日期]前回复此邮件。 感谢大家的参与! [签名] ``` ## 二、合作/业务开拓 ### 初次合作提案 ``` 主题:合作提案 — [公司名称] × [对方公司] 尊敬的[姓名/职位]: 您好!我是[公司名称]的[姓名],负责[职能方向]。 我们关注到贵司在[领域]的优秀成果,认为双方在[具体方向]上存在深度合作的可能。 我司在[核心优势]方面积累了[X年/案例],目前服务[客户类型],能够为贵司提供[价值点]。 如您感兴趣,我希望安排一次15-20分钟的沟通,进一步了解双方需求。 请问本周或下周,您方便的时间是? 期待您的回复,感谢您的时间! [签名] ``` ### 报价/方案发送 ``` 主题:[项目名称] 方案及报价 — [公司名称] [称呼],您好: 感谢您在[日期]与我们的交流。根据您的需求,请查收附件中的[方案/报价]。 方案亮点: • [亮点1] • [亮点2] • [亮点3] 报价有效期至[日期]。 如有任何问题,欢迎随时联系我([电话/微信])。期待进一步沟通! [签名] [附件:XXX方案.pdf / 报价单.xlsx] ``` ## 三、内部沟通 ### 工作进度汇报 ``` 主题:[项目名称] 进度汇报 — [日期] [上级姓名]好: 以下是[项目/工作]本周进展汇报: 【已完成】 ✅ [事项1] ✅ [事项2] 【进行中】 🔄 [事项3](预计[日期]完成) 【待处理/需支持】 ⚠️ [问题/阻碍] — 需要[具体支持] 下周计划:[简要说明] 如有调整建议,请随时告知。谢谢! [签名] ``` ### 跨部门协作请求 ``` 主题:协助请求:[事项名称] [称呼],您好: 因[项目/业务需求],需要请[部门]同事协助以下事项: 协助内容:[具体说明] 所需时间:约[X小时/X天] 期望完成时间:[日期] 如有任何不便,请告知,我们可以进一步协商。 感谢您的支持,期待合作愉快! [签名] ``` FILE:references/subject-line-guide.md # 主题行写作指南 ## 为什么主题行至关重要 - 决定邮件是否被打开(尤其外部邮件) - 帮助收件人快速排优先级 - 便于日后检索 - 专业形象的第一印象 ## 主题行公式 ### 公式1:[类型] + [核心内容] ``` 邀请:XX产品发布会 — 2026年4月20日 请求:审批《XX合同》— 需在4月18日前确认 通知:系统维护公告 — 4月19日凌晨2-4点 跟进:关于XX项目报价的后续确认 道歉:就XX延误向您致歉及补救方案 ``` ### 公式2:[紧急程度] + [内容](仅真正紧急时使用) ``` 【紧急】XX服务器故障处理进展 【今日截止】XX合同签署提醒 ``` ### 公式3:回复引用 ``` Re: 关于XX合作的初步方案 — 补充说明 Fwd: XX文件 — 请查阅并签字 ``` ## 主题行长度建议 - **最佳**:15-30字(完整显示于大多数邮件客户端) - **最长**:50字以内 - **避免**:超过60字(移动端截断) ## 好/坏主题行对比 | ❌ 模糊/差 | ✅ 清晰/好 | |-----------|-----------| | 你好 | 合作提案:AI工具采购 — 请审阅 | | 关于那个事情 | 跟进:3月15日报价确认 | | 重要通知!!! | 通知:办公室搬迁安排 — 5月1日起 | | 请回复 | 请求:确认4月20日会议出席 | | 方案 | 附件:XX项目实施方案 v2.0 | | 问题 | 咨询:合同第5条款解释 | ## 特殊场景 ### 群发邮件 在主题中注明,避免误解: ``` [全员] 2026年清明节放假通知 [销售团队] 4月业绩目标调整说明 ``` ### 系列邮件 加入编号或阶段标识: ``` [第1封] XX项目启动:背景与目标 [第2封] XX项目启动:执行计划与分工 ``` ### 带附件 主动在主题中标注: ``` 附:Q1财务报告 — 请于4月25日前签字确认 ``` ## 行业特定主题模板 **科技/产品** - 产品需求评审:[功能名] — 请在[日期]前反馈 - Bug报告:[模块名] 在[版本]中的[问题描述] **销售/商务** - [客户名] × [公司名] 合作方案 — 初稿提交 - 续约提醒:[合同名称] 将于[日期]到期 **HR/行政** - 录用通知:[职位名称] — [候选人姓名] - [部门] 绩效评估表 — 请于[日期]填写完成
Use when developing Vite plugins: scaffolding plugin structure, implementing hooks (transform, resolveId, load, buildStart, etc.), handling virtual modules,...
---
name: vite-plugin-dev
description: >
Use when developing Vite plugins: scaffolding plugin structure, implementing
hooks (transform, resolveId, load, buildStart, etc.), handling virtual modules,
HMR integration, SSR compatibility, and publishing to npm. Covers Vite 5/6 plugin API.
---
# Vite Plugin Development
## When to Use
- Building a custom Vite plugin from scratch
- Implementing transform/resolve hooks for special file types
- Creating virtual modules or runtime injections
- Adding HMR (Hot Module Replacement) support
- Making plugins SSR-compatible
- Publishing a plugin package to npm
## Core Workflow
### 1. Plugin Scaffold
Every Vite plugin is a factory function returning a plugin object:
```ts
import type { Plugin } from 'vite'
export interface MyPluginOptions {
include?: string | RegExp | (string | RegExp)[]
exclude?: string | RegExp | (string | RegExp)[]
}
export default function myPlugin(options: MyPluginOptions = {}): Plugin {
return {
name: 'vite-plugin-my', // required, unique name
enforce: 'pre', // 'pre' | 'post' | undefined
apply: 'build', // 'build' | 'serve' | ((config, env) => bool)
// lifecycle hooks below...
}
}
```
### 2. Key Hooks
| Hook | Phase | Purpose |
|------|-------|---------|
| `config` | Both | Modify resolved config |
| `configResolved` | Both | Read final config |
| `buildStart` | Both | Initialize plugin state |
| `resolveId` | Both | Custom module resolution |
| `load` | Both | Custom module content |
| `transform` | Both | Code transformation |
| `generateBundle` | Build | Post-process output |
| `configureServer` | Serve | Extend dev server |
| `handleHotUpdate` | Serve | Custom HMR logic |
### 3. Transform Hook Pattern
```ts
transform(code, id) {
if (!id.endsWith('.myext')) return null // return null = skip
const transformed = doTransform(code)
return {
code: transformed,
map: null, // provide sourcemap when possible
}
},
```
### 4. Virtual Modules
```ts
const VIRTUAL_ID = 'virtual:my-module'
const RESOLVED_ID = '\0' + VIRTUAL_ID // prefix \0 to avoid collisions
resolveId(id) {
if (id === VIRTUAL_ID) return RESOLVED_ID
},
load(id) {
if (id === RESOLVED_ID) {
return `export const data = JSON.stringify(myData)`
}
},
```
### 5. HMR Support
```ts
handleHotUpdate({ file, server }) {
if (file.endsWith('.myext')) {
server.ws.send({ type: 'full-reload' })
return [] // prevent default HMR
}
},
```
### 6. SSR Compatibility Checklist
- Check `options.ssr` in `transform` / `load` hooks
- Avoid browser-only APIs at transform time
- Mark side-effect-free modules: set `moduleSideEffects: false`
- Test with `vite build --ssr`
### 7. Testing Strategy
```ts
// Use vite's createServer for integration tests
import { createServer } from 'vite'
const server = await createServer({ plugins: [myPlugin()] })
const mod = await server.ssrLoadModule('/src/test.myext')
```
## Publishing Checklist
- [ ] `package.json` `main` + `module` + `types` fields set
- [ ] `peerDependencies`: `"vite": "^5.0.0 || ^6.0.0"`
- [ ] README with usage example + options table
- [ ] Export TypeScript types
- [ ] Test against Vite 5 and 6
- [ ] `vite-plugin-` prefix in package name (ecosystem convention)
FILE:references/hooks-reference.md
# Vite Plugin Hooks Reference
## Full Hook Execution Order
### Dev Server (serve)
1. config
2. configResolved
3. options (rollup)
4. buildStart
5. configureServer ← add middleware here
6. Per request: resolveId → load → transform
7. handleHotUpdate (on file change)
8. configurePreviewServer (vite preview)
### Build
1. config
2. configResolved
3. options (rollup)
4. buildStart
5. Per module: resolveId → load → transform
6. moduleParsed
7. buildEnd
8. generateBundle
9. writeBundle
10. closeBundle
## Hook Signatures
```ts
interface Plugin {
name: string
enforce?: 'pre' | 'post'
apply?: 'build' | 'serve' | ((config: UserConfig, env: ConfigEnv) => boolean)
// Config
config?: (config: UserConfig, env: ConfigEnv) =>
UserConfig | null | void | Promise<UserConfig | null | void>
configResolved?: (config: ResolvedConfig) => void | Promise<void>
// Server
configureServer?: (server: ViteDevServer) => (() => void) | void | Promise<...>
handleHotUpdate?: (ctx: HmrContext) => Array<ModuleNode> | void | Promise<...>
// Build
buildStart?: (options: InputOptions) => void | Promise<void>
buildEnd?: (error?: Error) => void | Promise<void>
closeBundle?: () => void | Promise<void>
// Module
resolveId?: (
source: string,
importer: string | undefined,
options: { ssr?: boolean }
) => ResolveIdResult | Promise<ResolveIdResult>
load?: (
id: string,
options?: { ssr?: boolean }
) => LoadResult | Promise<LoadResult>
transform?: (
code: string,
id: string,
options?: { ssr?: boolean }
) => TransformResult | Promise<TransformResult>
// Bundle
generateBundle?: (
options: OutputOptions,
bundle: OutputBundle,
isWrite: boolean
) => void | Promise<void>
}
```
## enforce Order
Plugins execute in this order:
1. Plugins with `enforce: 'pre'`
2. Vite core plugins
3. Plugins without `enforce`
4. Vite build plugins
5. Plugins with `enforce: 'post'`
## apply Examples
```ts
// Only run during build
apply: 'build'
// Only run during dev
apply: 'serve'
// Conditional
apply(config, { command }) {
return command === 'build' && !config.build?.ssr
}
```
FILE:references/plugin-patterns.md
# Vite Plugin Patterns & Examples
## Pattern 1: File Type Transformer
Transform `.yaml` files into JS objects:
```ts
import { parse } from 'yaml'
import type { Plugin } from 'vite'
export default function yamlPlugin(): Plugin {
return {
name: 'vite-plugin-yaml',
transform(code, id) {
if (!id.endsWith('.yaml') && !id.endsWith('.yml')) return null
try {
const data = parse(code)
return {
code: `export default JSON.stringify(data)`,
map: { mappings: '' },
}
} catch (e) {
this.error(`Failed to parse YAML: e.message`)
}
},
}
}
```
## Pattern 2: Virtual Module with Runtime Data
Inject build-time data accessible as a module:
```ts
export default function buildInfoPlugin(): Plugin {
const VIRTUAL = 'virtual:build-info'
const RESOLVED = '\0virtual:build-info'
return {
name: 'vite-plugin-build-info',
resolveId(id) {
if (id === VIRTUAL) return RESOLVED
},
load(id) {
if (id !== RESOLVED) return
return `
export const buildTime = Date.now()
export const version = JSON.stringify(process.env.npm_package_version)
export const mode = JSON.stringify(process.env.NODE_ENV)
`
},
}
}
// Usage in app:
// import { buildTime, version } from 'virtual:build-info'
```
## Pattern 3: Dev Server Middleware
Add custom API routes in dev mode:
```ts
export default function mockApiPlugin(): Plugin {
return {
name: 'vite-plugin-mock-api',
apply: 'serve',
configureServer(server) {
server.middlewares.use('/api/hello', (req, res) => {
res.setHeader('Content-Type', 'application/json')
res.end(JSON.stringify({ message: 'Hello from mock API' }))
})
},
}
}
```
## Pattern 4: Asset Processing
Process and emit custom assets:
```ts
export default function svgSpritePlugin(): Plugin {
const sprites: string[] = []
return {
name: 'vite-plugin-svg-sprite',
transform(code, id) {
if (!id.endsWith('.svg')) return null
sprites.push(code)
return { code: `export default ""`, map: null }
},
generateBundle() {
this.emitFile({
type: 'asset',
fileName: 'sprite.svg',
source: `<svg>sprites.join('')</svg>`,
})
},
}
}
```
## Pattern 5: Config Injection
Expose env/config to client code:
```ts
export default function configPlugin(userConfig: Record<string, unknown>): Plugin {
return {
name: 'vite-plugin-config',
config() {
return {
define: {
__APP_CONFIG__: JSON.stringify(userConfig),
},
}
},
}
}
```
## Testing Setup
```ts
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import myPlugin from './src/index'
export default defineConfig({
plugins: [myPlugin()],
test: { environment: 'node' },
})
// plugin.test.ts
import { build } from 'vite'
import { test, expect } from 'vitest'
test('transforms .myext files', async () => {
const result = await build({
plugins: [myPlugin()],
build: { write: false },
})
expect(result).toBeDefined()
})
```
FILE:references/publishing-guide.md
# Publishing a Vite Plugin to npm
## Project Structure
```
vite-plugin-my/
├── src/
│ └── index.ts # Plugin entry
├── dist/ # Build output (generated)
├── package.json
├── tsconfig.json
├── README.md
└── CHANGELOG.md
```
## package.json Template
```json
{
"name": "vite-plugin-my",
"version": "1.0.0",
"description": "A Vite plugin that does X",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"files": ["dist", "README.md", "CHANGELOG.md"],
"keywords": ["vite", "vite-plugin"],
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0"
},
"peerDependenciesMeta": {
"vite": { "optional": true }
},
"devDependencies": {
"vite": "^6.0.0",
"typescript": "^5.0.0",
"tsup": "^8.0.0",
"vitest": "^2.0.0"
},
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts",
"test": "vitest run",
"prepublishOnly": "npm run build && npm test"
}
}
```
## tsup Build Config (tsup.config.ts)
```ts
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm', 'cjs'],
dts: true,
clean: true,
external: ['vite'],
})
```
## TypeScript Config
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"declaration": true,
"outDir": "dist"
},
"include": ["src"]
}
```
## README Template
```markdown
# vite-plugin-my
> One-line description of what the plugin does.
## Install
npm i -D vite-plugin-my
## Usage
// vite.config.ts
import myPlugin from 'vite-plugin-my'
export default defineConfig({
plugins: [myPlugin({ option: 'value' })]
})
## Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| include | string/RegExp | '**/*' | Files to include |
## License
MIT
```
## Publishing Checklist
- [ ] `npm run build` succeeds, dist/ has .js, .cjs, .d.ts
- [ ] `npm pack --dry-run` shows expected files only
- [ ] Tested with both Vite 5 and Vite 6
- [ ] CHANGELOG.md updated
- [ ] Git tag created: `git tag v1.0.0`
- [ ] `npm publish --access public`
- [ ] Announce on Twitter/X, Vite Discord #plugins channel
Create personalized Chinese fitness training plans based on goals, fitness level, available equipment, and schedule. Use when someone needs a structured work...
--- name: fitness-coach-cn description: > Create personalized Chinese fitness training plans based on goals, fitness level, available equipment, and schedule. Use when someone needs a structured workout program for weight loss, muscle building, endurance, or general fitness — with exercise instructions, weekly schedules, and progress tracking. --- # 健身训练计划制定助手 ## 使用场景 - 根据个人情况制定个性化训练计划 - 解释动作要领和常见错误 - 规划减脂/增肌/塑形/提升耐力等目标 - 提供营养搭配和恢复建议 ## 信息收集工作流 开始前先询问用户: ``` 📋 请提供以下信息(可以简短回答): 1. 健身目标:减脂 / 增肌 / 塑形 / 提升体能 / 其他? 2. 当前健身水平:新手(<3个月)/ 初级(3-12个月)/ 中级(1-3年)/ 高级? 3. 可用设备:无器械(居家)/ 哑铃 / 健身房全套 / 其他? 4. 每周可训练天数:2-3天 / 4-5天 / 6天+? 5. 每次训练时长:30分钟 / 45分钟 / 60-90分钟? 6. 年龄、性别(可选): 7. 身体限制或受伤史(可选): ``` ## 训练计划生成框架 ### 减脂计划模板(示例:新手 · 3天/周 · 无器械) ```markdown ## 🔥 减脂训练计划 | 新手 | 3天/周 | 居家 **训练原则**:HIIT + 基础力量,提升代谢率 --- ### 第一天:全身 HIIT(周一)40分钟 **热身** 5分钟 - 原地高抬腿 × 30秒 × 2组 - 动态拉伸(肩、髋、踝) **主训练** 30分钟(循环3轮,组间休息60秒) 1. 深蹲 × 15个 2. 俯卧撑 × 10个(可膝盖着地) 3. 弓步蹲(左右各10次) 4. 平板支撑 × 30秒 5. 波比跳 × 8个 6. 卷腹 × 20个 **拉伸放松** 5分钟 --- ### 第二天:有氧+核心(周三)40分钟 ... ### 第三天:下肢力量(周六)45分钟 ... ``` ### 增肌计划模板(示例:中级 · 5天/周 · 健身房) ```markdown ## 💪 增肌训练计划 | 中级 | 5天推拉腿分化 **训练原则**:渐进超负荷,每2周增加重量或次数 | 日期 | 训练内容 | |------|---------| | 周一 | Push(胸、肩、三头)| | 周二 | Pull(背、二头)| | 周三 | Legs(腿、臀)| | 周四 | 休息 / 主动恢复 | | 周五 | Push | | 周六 | Pull | | 周日 | Legs | ### Push Day 详细安排 1. 卧推 4组 × 6-8次(主力) 2. 哑铃上斜飞鸟 3组 × 10-12次 3. 坐姿推举 3组 × 8-10次 4. 侧平举 3组 × 12-15次 5. 绳索下压 3组 × 12-15次 ``` ## 动作要领说明格式 ```markdown ### 深蹲(Squat) **目标肌群**:股四头肌、臀大肌、腘绳肌 **正确姿势** - 双脚与肩同宽或略宽,脚尖微外展(15-30°) - 挺胸收腹,保持腰椎自然弓度 - 下蹲时膝盖跟随脚尖方向,不内扣 - 大腿下蹲至少平行地面 **常见错误** ⚠️ - ❌ 膝盖内扣(增加损伤风险) - ❌ 背部圆背(腰椎受压) - ❌ 脚跟抬起(踝关节灵活性不足) **进阶/退阶** - 新手:坐椅深蹲(后方放椅子作辅助) - 进阶:杠铃深蹲、保加利亚深蹲 ``` ## 进度追踪建议 ```markdown ## 健身日志模板 日期:____ 训练内容:____ | 动作 | 组数 | 重量 | 次数 | 感受 | |------|------|------|------|------| | 深蹲 | 组1 | 60kg | 8次 | 💪 | | | 组2 | 60kg | 7次 | 😤 | 本次训练评分(1-10):____ 恢复状态(睡眠/酸痛):____ ``` ## 营养搭配要点 | 目标 | 热量建议 | 蛋白质 | 注意事项 | |------|---------|--------|---------| | 减脂 | 每日TDEE -300~500kcal | 1.6-2.0g/kg体重 | 不低于1200kcal | | 增肌 | 每日TDEE +200~300kcal | 1.8-2.2g/kg体重 | 训练后30分钟补充蛋白 | | 维持 | 等于TDEE | 1.4-1.8g/kg体重 | 均衡饮食 | ## 参考文件 查看 `references/` 获取居家/健身房动作库、常见伤病预防和分化方案参考。 FILE:references/home-workout-library.md # 居家无器械动作库 ## 上肢动作 ### 俯卧撑系列 | 难度 | 动作 | 目标肌群 | |------|------|---------| | 入门 | 膝盖俯卧撑 | 胸、三头 | | 初级 | 标准俯卧撑 | 胸、肩、三头 | | 中级 | 宽距俯卧撑 | 胸大肌外侧 | | 中级 | 窄距俯卧撑 | 三头肌 | | 高级 | 钻石俯卧撑 | 三头 | | 高级 | 弓手俯卧撑 | 单侧胸+三头 | ### 拉力动作(需门框引体或弹力带) - 弹力带坐姿划船 - 俯身哑铃划船(如有哑铃) - 毛巾引体辅助 ## 下肢动作 ### 基础系列(无器械) | 动作 | 组数×次数 | 重点 | |------|----------|------| | 深蹲 | 3×15-20 | 全程保持膝盖不内扣 | | 臀桥 | 3×20 | 顶峰收缩停留1秒 | | 弓步蹲 | 3×12(每侧)| 前膝不超脚尖 | | 侧向弓步 | 3×10(每侧)| 内收肌拉伸 | | 单腿臀桥 | 3×12(每侧)| 进阶臀桥 | | 壁坐 | 3×30-60秒 | 静态耐力 | ### 爆发力系列 | 动作 | 训练效果 | |------|---------| | 跳绳 | 有氧+协调 | | 跳深蹲 | 下肢爆发 | | 箱跳(用台阶)| 爆发力 | | 横向跳 | 敏捷性 | ## 核心动作 ### 稳定性 - 平板支撑:30-60秒,进阶→侧平板、平板撑触肩 - 超人式:俯卧,对侧手脚抬起,保持3秒 - 死虫式:仰卧,对侧手腿延展 ### 动态核心 - 卷腹(注意:不要用力拉颈部) - 自行车卷腹 - 俄罗斯转体 - 登山者 ## HIIT 常用组合 ### 20/10 Tabata(每动作8轮 = 4分钟) ``` 波比跳 → 休息 深蹲跳 → 休息 ``` ### 40/20 AMRAP(尽可能多完成) ``` 5轮以下动作循环(12分钟): - 俯卧撑 × 10 - 深蹲 × 15 - 登山者 × 20(每侧10) - 跳绳 × 30秒 ``` FILE:references/injury-prevention-nutrition.md # 运动伤病预防与营养指南 ## 常见运动损伤预防 ### 膝盖损伤预防 **高风险动作**:深蹲、弓步蹲、跑步 **预防措施**: - 深蹲前激活臀中肌(侧卧抬腿、蚌式开合 × 15次) - 控制训练量递增速率(每周不超过10%) - 加强股四头肌和腘绳肌力量均衡 - 避免膝盖内扣(Valgus Collapse) ### 腰背部损伤预防 **高风险动作**:硬拉、深蹲、卷腹 **预防措施**: - 每次训练前做5分钟核心激活(平板支撑、死虫式) - 硬拉注意保持中立脊柱,不弓背 - 腰椎有问题者避免仰卧起坐,改用卷腹 - 加强核心稳定性训练 ### 肩部损伤预防 **高风险动作**:卧推、肩推、引体向上 **预防措施**: - 加强旋转肌袖训练(弹力带外旋、面拉) - 推拉比例建议1:1.5(更多拉力动作) - 避免肩推时头部前伸 ## 热身与拉伸 ### 动态热身(训练前,5-8分钟) ``` 关节活动: - 颈部旋转 × 10圈 - 肩绕环 × 10次(前后各) - 髋部画圈 × 10次 - 踝关节绕环 × 10次 动态拉伸: - 腿摆动 × 10次(每侧) - 深蹲转体 × 8次 - 弓步蹲转体 × 8次(每侧) - 动态髋屈肌拉伸 × 8次 ``` ### 静态拉伸(训练后,5-10分钟) 保持每个姿势 20-30 秒,不要弹振 | 肌肉 | 拉伸动作 | |------|---------| | 股四头肌 | 站立屈膝向后拉 | | 腘绳肌 | 坐姿前弯 | | 髋屈肌 | 低弓步拉伸 | | 胸肌 | 门框拉伸 | | 背阔肌 | 悬挂拉伸或跪姿侧拉 | | 小腿 | 墙壁推顶拉伸 | ## 营养时机建议 ### 训练前(1-2小时) - 易消化碳水 + 适量蛋白质 - 例:香蕉 + 希腊酸奶 / 燕麦 + 鸡胸肉 ### 训练后(30-60分钟内) - 快速蛋白质 + 适量碳水 - 例:蛋白粉 + 米饭 / 鸡蛋 + 面包 ### 日常饮食原则 **减脂期** - 热量缺口 300-500kcal/天(不超过20%) - 高蛋白(1.8-2.2g/kg)保留肌肉 - 控制精制糖和加工食品 - 保持充足蔬菜和膳食纤维 **增肌期** - 热量盈余 200-300kcal/天(避免脂肪增加过多) - 蛋白质均匀分配在每餐(每餐20-40g) - 训练日适当增加碳水 ### 水分补充 - 每日基础饮水:体重(kg)× 30-35ml - 训练中:每15分钟补充150-250ml - 大量出汗时补充电解质 FILE:references/split-programs.md # 健身计划分化方案参考 ## 常见训练分化方式 ### 2天/周:全身训练 适合:健身新手、时间有限人群 ``` Day A(周一):全身 - 以推为主 - 深蹲 / 俯卧撑 / 肩推 / 核心 Day B(周四):全身 - 以拉为主 - 硬拉 / 划船 / 引体 / 核心 ``` ### 3天/周:全身分化 ``` Day 1(周一):全身训练A Day 2(周三):全身训练B Day 3(周六):全身训练C(更多有氧+弱项补强) ``` ### 4天/周:上下分化(Upper/Lower Split) ``` 周一:上肢(推+拉) 周二:下肢+核心 周四:上肢(变化角度/动作) 周五:下肢+有氧 ``` ### 5天/周:推拉腿(PPL) ``` 周一:Push(胸、肩、三头) 周二:Pull(背、二头) 周三:Legs(股四头、腘绳肌、臀、小腿) 周四:休息 周五:Push(变化动作) 周六:Pull(变化动作) 周日:Legs(变化动作) ``` ### 6天/周:Arnold Split ``` 周一:胸 + 背 周二:肩 + 手臂 周三:腿 + 核心 周四:胸 + 背 周五:肩 + 手臂 周六:腿 + 核心 周日:完全休息 ``` ## 渐进超负荷原则 增肌的核心:每次训练都要比上次有进步 ``` 方法1:增加重量(每2-3周增加2.5-5kg) 方法2:增加次数(目标范围8-12次,达到上限再加重) 方法3:增加组数(从3组进阶到4-5组) 方法4:减少休息时间(从2分钟减至1分30秒) 方法5:增加训练频率(每块肌肉从1次/周到2次/周) ``` ## 恢复与休息 | 训练强度 | 推荐恢复时间 | |---------|------------| | 低强度有氧 | 24小时 | | 中等强度力量 | 48小时 | | 高强度力量(大肌群)| 72小时 | | 高强度 HIIT | 48-72小时 | ### 主动恢复日(Active Recovery) - 轻松步行 20-30 分钟 - 瑜伽或拉伸 - 游泳(轻松节奏) - 泡沫轴放松 ## 常见目标的训练重点 | 目标 | 重量区间 | 次数区间 | 组间休息 | |------|---------|---------|---------| | 最大力量 | 85-100% 1RM | 1-5次 | 3-5分钟 | | 增肌(肌肥大)| 67-85% 1RM | 6-12次 | 60-90秒 | | 肌耐力 | <67% 1RM | 15次+ | 30-60秒 | | 减脂 | 中等重量 | 12-20次 | 30-60秒 |
Automatically generate professional Chinese weekly/daily work reports from bullet-point inputs. Use when you need to transform raw task notes into polished 周...
--- name: weekly-report-cn description: > Automatically generate professional Chinese weekly/daily work reports from bullet-point inputs. Use when you need to transform raw task notes into polished 周报/日报 with structured sections, highlight achievements, and suggest next steps. Also supports customization for different team styles. --- # 中文周报/日报自动生成助手 ## 使用场景 - 将碎片化工作记录整理成正式周报 - 快速生成符合公司格式的日报 - 提炼工作亮点,突出价值贡献 - 规划下周工作目标和重点 ## 标准工作流 ### 第一步:收集输入信息 向用户询问以下内容(或从用户提供的原始笔记中提取): ``` 本周/今日完成的工作(关键词或简短描述即可): - [工作项1] - [工作项2] ... 遇到的问题或风险(可选): 工作量占比或优先级(可选): 下周/明日计划(可选): ``` ### 第二步:结构化生成 按以下模板生成周报: ```markdown # 周报 | [姓名] | [日期范围] ## 一、本周工作完成情况 ### 1. [主要项目/模块名称](占比 XX%) - **[成果描述]**:[具体说明,量化数据优先] - 完成了 [具体任务],实现 [具体效果/指标] ### 2. [次要项目/日常工作](占比 XX%) - [工作项描述] ## 二、重点成果与亮点 - ✅ [最重要成果1] - ✅ [最重要成果2] ## 三、问题与风险 - ⚠️ [问题描述] → 当前处理方案:[方案] ## 四、下周工作计划 1. [计划1] — 预计完成时间:[时间] 2. [计划2] ## 五、需要支持/协调的事项(可选) - [需要其他人/部门配合的事项] ``` ### 第三步:润色优化 生成后自动应用以下规则: - 动词开头:完成、推进、优化、调研、输出、协调、落地 - 量化数据:涉及指标时补充百分比/数量/时间 - 突出价值:将"做了XX"改为"通过XX实现了YY" - 专业术语:保持行业用语准确 ## 日报简化版 ```markdown ## 日报 | [日期] **今日完成** - [ ] [工作项1](完成✅ / 进行中🔄) - [ ] [工作项2] **明日计划** - [ ] [计划1] **需要协调** (无则省略) - [协调事项] ``` ## 不同风格适配 | 团队风格 | 调整方向 | |---------|---------| | 技术团队 | 多用数据指标,突出技术难点和解决方案 | | 业务团队 | 聚焦业务影响,用户/营收/转化等指标 | | 管理层汇报 | 精简为3-5条核心亮点,BLUF原则 | | 跨部门协作 | 强调依赖项、进度里程碑和风险 | ## 质量检查清单 - [ ] 是否有至少一个量化指标 - [ ] 动词是否清晰(避免"参与、了解、配合"等模糊词) - [ ] 格式是否整洁(标题层级、缩进一致) - [ ] 下周计划是否具体可执行 - [ ] 篇幅适当(周报 300-600 字,日报 100-200 字) ## 参考文件 查看 `references/` 获取周报模板、词汇表和示例。 FILE:references/report-examples.md # 周报/日报完整示例 ## 示例一:技术研发周报 --- # 周报 | 张三 | 2026.04.07 - 04.11 ## 一、本周工作完成情况 ### 1. 用户中心改版项目(占比 60%) - **完成登录模块重构**:将旧版 Session 认证迁移至 JWT 方案,接口响应时间从 450ms 降至 85ms,并通过全量回归测试 - **推进权限系统设计**:完成权限模型文档 v2.0 评审,获得架构师确认,进入开发阶段 ### 2. 日常维护与支持(占比 30%) - 修复生产环境 3 个高优 bug(#1024、#1031、#1038),均在 SLA 时限内完成 - 完成 2 次 Code Review,提出优化建议 8 条,其中 6 条已采纳 ### 3. 团队协作(占比 10%) - 输出《前端性能优化最佳实践》内部分享,参与人数 15 人 ## 二、重点成果与亮点 - ✅ JWT 改造上线,用户登录响应速度提升 81% - ✅ 生产 bug 零漏单,全部在 SLA 内闭环 ## 三、问题与风险 - ⚠️ 权限系统后端接口联调延期 2 天(后端人员请假)→ 已协调备用资源,预计下周二恢复正常进度 ## 四、下周工作计划 1. 完成权限系统前端开发 — 预计 04.15 完成 2. 开始用户中心首页 UI 实现 — 预计 04.16 启动 3. 参与 Q2 技术规划讨论 --- ## 示例二:产品运营日报 --- ## 日报 | 李四 | 2026.04.13(周一) **今日完成** - ✅ 整理上周用户反馈 47 条,归类分析后输出优先级排序报告 - ✅ 完成 4 月第二周推送内容策划(共 5 条),已提交审核 - 🔄 新功能灰度测试数据收集中,当前灰度 5%,数据正常 **明日计划** - [ ] 跟进推送内容审核结果,修改后排期上线 - [ ] 完成用户调研问卷设计(目标:发出 200 份) - [ ] 与研发对齐灰度扩量方案 **需要协调** - 请求数据团队本周三前提供上周留存率分析数据,用于月报 --- ## 示例三:管理层汇报版(精简) --- ## 周报摘要 | 王五 | 2026 W15 **核心成果(3条)** 1. 完成 Q2 预算方案,获管理层审批通过,节省预算 12% 2. 主导供应商评估,完成 3 家对比,推荐方案已提交 3. 新员工入职 2 人,完成 Onboarding 培训 **下周重点** - 启动 Q2 目标 OKR 对齐会议(04.15) - 跟进 IT 系统升级排期 **需要决策支持** - 供应商最终选择需总监在 04.16 前审批 --- FILE:references/vocabulary-guide.md # 周报撰写词汇与短语库 ## 高质量动词(开头动词) ### 完成类 - 完成、交付、落地、上线、发布 - 实现、产出、输出、完工 ### 推进类 - 推进、跟进、协调、对齐、同步 - 主导、牵头、组织、统筹 ### 优化类 - 优化、改善、提升、改进、精简 - 重构、调整、迭代、修复 ### 调研/分析类 - 调研、分析、评估、梳理、摸排 - 整理、汇总、复盘、拆解 ## 成果表达模板 ``` 通过 [做了什么],实现了 [什么结果],效率提升 [X]% 完成 [任务],较计划提前 [X] 天交付 主导 [项目] 落地,覆盖 [X] 个用户/部门 优化 [流程/系统],减少人工操作 [X] 小时/周 ``` ## 量化表达 | 场景 | 表达方式 | |------|---------| | 时间节省 | "缩短处理时长 40%","从 2 天压缩至 4 小时" | | 质量提升 | "错误率从 5% 降至 0.5%","通过率提升至 98%" | | 规模覆盖 | "覆盖 3 个部门,15 名同事","服务 2000+ 用户" | | 进度状态 | "完成 80%,预计本周五交付","已完成里程碑 M2" | ## 问题/风险描述模板 ``` 【风险】[问题描述] 影响范围:[范围] 当前状态:[处理进展] 应对方案:[解决方案] 预计解决时间:[时间] ``` ## 常见模糊表达 → 改进 | 模糊写法 ❌ | 清晰写法 ✅ | |-----------|-----------| | 参与了需求讨论 | 主导需求评审,输出需求文档 v1.0 | | 了解了竞品情况 | 完成 5 家竞品调研,整理对比分析报告 | | 配合了测试 | 协助测试团队完成功能测试,修复 12 个 bug | | 跟进了问题 | 跟进 XX 问题,已于周四完成修复并上线验证 | | 做了一些优化 | 优化数据库查询逻辑,接口响应时间从 800ms 降至 120ms | FILE:references/workflow-format.md # 周报工作流程与格式规范 ## 写作流程(5步法) ``` Step 1: 收集 (5分钟) 收集本周工作记录、聊天记录、任务系统截图 Step 2: 分类 (3分钟) 按项目/职能分组,标记完成/进行中/延期 Step 3: 量化 (5分钟) 为每项工作找到对应数据指标 Step 4: 起草 (10分钟) 套用模板,用动词开头描述每项工作 Step 5: 校对 (2分钟) 检查格式、数据、逻辑 ``` ## 不同场景的格式调整 ### 项目制团队 重点:里程碑状态 + 风险预警 + 资源需求 ```markdown ## 项目进展 | 项目 | 本周状态 | 完成度 | 风险 | |------|---------|--------|------| | 项目A | 🟢 正常 | 75% | 无 | | 项目B | 🟡 关注 | 40% | 人力不足 | | 项目C | 🔴 预警 | 20% | 需求变更 | ``` ### 职能型团队(市场/运营) 重点:数据指标 + 活动结果 + 渠道表现 ```markdown ## 核心指标本周数据 - DAU:12,450(环比 +8.2%) - 转化率:3.4%(环比 -0.3%,原因:流量结构变化) - 新增用户:856(完成目标的 107%) ``` ### 研发团队 重点:技术债、Bug 数量、代码质量 ```markdown ## 技术指标 - 新增 Bug:12,关闭 Bug:15,遗留:3(均为低优) - 代码提交:47 commits,Code Review:5 次 - 单测覆盖率:从 72% 提升至 78% ``` ## OKR 对齐写法 每项工作尝试关联 KR: ```markdown ### [KR1: 用户满意度 NPS ≥ 45] - 完成用户投诉分类分析,识别 TOP3 痛点 - 推动客服 SLA 优化,响应时间从 4h 降至 2h ### [KR2: Q2 营收目标 500万] - 完成 3 个大客户方案演示,2 个进入谈判阶段 ``` ## 时间管理建议 - **每日 10 分钟**:记录工作流水账(不要等到周五才整理) - **周五下午**:整理成正式周报,不超过 30 分钟 - **推荐工具**:用备忘录/Notion/飞书日志记录日常,周末汇总 ## 常见格式错误 | 错误 | 正确 | |------|------| | 标题缺少日期 | 始终包含日期范围 | | 工作项过于简短 | 每项至少说明做了什么+结果/状态 | | 没有下周计划 | 必须包含至少2-3条具体计划 | | 格式混乱(中英混用层级) | 统一使用中文标题,层级一致 |
Apply Next.js 15 best practices and modern patterns including App Router, Server Components, Server Actions, caching strategies, and performance optimization...
---
name: nextjs-patterns
description: >
Apply Next.js 15 best practices and modern patterns including App Router,
Server Components, Server Actions, caching strategies, and performance
optimization. Use when building or reviewing Next.js 15 applications to
ensure idiomatic, production-ready code.
---
# Next.js 15 Best Practices
## Core Principles
1. **Default to Server Components** — only add `"use client"` when you need interactivity or browser APIs
2. **Colocate by feature** — keep components, hooks, and utils near the routes that use them
3. **Type everything** — leverage TypeScript with strict mode enabled
4. **Cache deliberately** — understand the four caching layers and opt in/out explicitly
## Project Structure
```
app/
(marketing)/ # route group: no URL segment
page.tsx
(dashboard)/
layout.tsx
[id]/
page.tsx
api/
route.ts
components/
ui/ # shared, "dumb" UI components
features/ # feature-specific components
lib/
db.ts # database client (singleton)
auth.ts # auth helpers
utils.ts
```
## Server vs. Client Components
```tsx
// ✅ Server Component (default) — runs on server, no JS sent to client
export default async function ProductList() {
const products = await db.product.findMany()
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}
// ✅ Client Component — only when needed
"use client"
import { useState } from "react"
export function Counter() {
const [n, setN] = useState(0)
return <button onClick={() => setN(n + 1)}>{n}</button>
}
```
## Data Fetching Patterns
```tsx
// Parallel fetching (avoid sequential waterfalls)
export default async function Dashboard() {
const [user, stats] = await Promise.all([
fetchUser(),
fetchStats(),
])
return <View user={user} stats={stats} />
}
// fetch with cache control
const data = await fetch("https://api.example.com/data", {
next: { revalidate: 60 }, // ISR: revalidate every 60s
// cache: "no-store" // always fresh
// cache: "force-cache" // static, until manual revalidation
})
```
## Server Actions
```tsx
// app/actions.ts
"use server"
import { revalidatePath } from "next/cache"
export async function createPost(formData: FormData) {
const title = formData.get("title") as string
await db.post.create({ data: { title } })
revalidatePath("/posts")
}
// app/posts/new/page.tsx
import { createPost } from "../actions"
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<button type="submit">Create</button>
</form>
)
}
```
## Metadata & SEO
```tsx
// Static metadata
export const metadata = {
title: "My App",
description: "...",
}
// Dynamic metadata
export async function generateMetadata({ params }) {
const post = await fetchPost(params.slug)
return { title: post.title, description: post.excerpt }
}
```
## Error & Loading States
```tsx
// app/posts/loading.tsx — automatic Suspense boundary
export default function Loading() {
return <Skeleton />
}
// app/posts/error.tsx — automatic error boundary
"use client"
export default function Error({ error, reset }) {
return <button onClick={reset}>Retry: {error.message}</button>
}
```
## Performance Checklist
- [ ] Images use `next/image` with explicit `width`/`height`
- [ ] Fonts use `next/font` (zero layout shift)
- [ ] Dynamic imports for heavy client components: `dynamic(() => import(...))`
- [ ] `generateStaticParams` for known dynamic routes
- [ ] Bundle analyzer run: `ANALYZE=true next build`
- [ ] Partial Prerendering (PPR) considered for mixed static/dynamic pages
## References
See `references/` folder for routing patterns, caching deep-dive, and migration guide.
FILE:references/caching-guide.md
# Next.js 15 Caching Deep Dive
## The Four Caching Layers
### 1. Request Memoization (in-memory, per request)
Automatically deduplicates identical `fetch()` calls within a single render tree.
```tsx
// Both components call fetchUser(id) — only ONE network request is made
async function Avatar({ id }) { const u = await fetchUser(id); return <img src={u.avatar} /> }
async function Name({ id }) { const u = await fetchUser(id); return <span>{u.name}</span> }
```
### 2. Data Cache (persistent, cross-request)
`fetch()` responses are stored on the server and reused across requests and deployments.
```tsx
// Static — cached indefinitely
fetch(url)
fetch(url, { cache: "force-cache" })
// Time-based revalidation (ISR)
fetch(url, { next: { revalidate: 3600 } }) // revalidate every hour
// Always fresh — no caching
fetch(url, { cache: "no-store" })
fetch(url, { next: { revalidate: 0 } })
```
### 3. Full Route Cache (build-time static rendering)
Pages rendered at build time are stored as static HTML+RSC payload.
```tsx
// Force dynamic rendering for this route
export const dynamic = "force-dynamic"
// Custom revalidation period for the whole route
export const revalidate = 60
```
### 4. Router Cache (client-side, per session)
Browser caches RSC payloads for instant back/forward navigation.
- Static routes: cached for 5 minutes
- Dynamic routes: cached for 30 seconds
## On-Demand Revalidation
```ts
// Revalidate by path
import { revalidatePath } from "next/cache"
revalidatePath("/blog")
revalidatePath("/blog/[slug]", "page")
// Revalidate by cache tag
import { revalidateTag } from "next/cache"
revalidateTag("posts")
// Tagging fetches
fetch(url, { next: { tags: ["posts"] } })
```
## unstable_cache for Non-Fetch Data
```ts
import { unstable_cache } from "next/cache"
const getCachedUser = unstable_cache(
async (id: string) => db.user.findUnique({ where: { id } }),
["user"],
{ revalidate: 300, tags: ["users"] }
)
```
## Common Pitfalls
| Mistake | Fix |
|---------|-----|
| Using `fetch` inside `useEffect` | Move to Server Component or use React Query |
| Forgetting `revalidatePath` after mutations | Call in Server Action after every write |
| Over-caching user-specific data | Add `cache: "no-store"` or check `cookies()`/`headers()` |
| Sequential awaits | Use `Promise.all()` for parallel fetching |
FILE:references/migration-config.md
# Next.js 15 Migration & Configuration Guide
## Upgrading to Next.js 15
```bash
npx @next/codemod@canary upgrade latest
# or manual:
npm install next@latest react@latest react-dom@latest
```
## Key Breaking Changes (14 → 15)
### 1. Async Request APIs (Breaking)
`cookies()`, `headers()`, `params`, `searchParams` are now async:
```tsx
// Before (Next.js 14)
export default function Page({ params }) {
const { id } = params
}
// After (Next.js 15)
export default async function Page({ params }) {
const { id } = await params
}
// cookies and headers
import { cookies, headers } from "next/headers"
const cookieStore = await cookies()
const headersList = await headers()
```
### 2. Caching Defaults Changed
`fetch()` no longer caches by default (was `force-cache`, now `no-store`).
Add `{ cache: "force-cache" }` or set route-level `export const fetchCache = "default-cache"` to restore.
### 3. React 19 Compatibility
Next.js 15 supports React 19. New hooks available:
- `useActionState` (replaces `useFormState`)
- `useFormStatus`
- `use()` for reading promises/context
## next.config.ts (TypeScript config)
```ts
import type { NextConfig } from "next"
const config: NextConfig = {
experimental: {
ppr: "incremental", // Partial Prerendering
reactCompiler: true, // React Compiler (auto-memoization)
},
images: {
remotePatterns: [
{ protocol: "https", hostname: "images.example.com" },
],
},
logging: {
fetches: { fullUrl: true }, // Log all fetch calls in dev
},
}
export default config
```
## Environment Variables
```bash
# .env.local
DATABASE_URL="postgresql://..."
NEXT_PUBLIC_API_URL="https://api.example.com" # exposed to browser
```
```ts
// Access server-side
process.env.DATABASE_URL
// Access client-side (only NEXT_PUBLIC_ prefix)
process.env.NEXT_PUBLIC_API_URL
```
## Turbopack (Default in Dev)
```bash
next dev # uses Turbopack by default in Next.js 15
next dev --turbopack # explicit flag (same behavior)
next build # Webpack still default for production builds
```
## TypeScript Path Aliases
```json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"@/components/*": ["./components/*"],
"@/lib/*": ["./lib/*"]
}
}
}
```
FILE:references/routing-patterns.md
# Next.js 15 Routing Patterns
## App Router Fundamentals
| File | Purpose |
|---------------|----------------------------------------------|
| `page.tsx` | Unique UI for a route, makes it publicly accessible |
| `layout.tsx` | Shared UI; does NOT re-render on navigation |
| `template.tsx`| Like layout but re-renders on navigation |
| `loading.tsx` | Instant loading state (React Suspense) |
| `error.tsx` | Error UI boundary (must be Client Component) |
| `not-found.tsx`| 404 UI |
| `route.ts` | API endpoint (GET, POST, etc.) |
## Route Groups
```
app/
(auth)/
login/page.tsx → /login
register/page.tsx → /register
(app)/
layout.tsx ← shared auth-required layout
dashboard/page.tsx → /dashboard
```
Route groups `(name)` organize routes without affecting the URL.
## Dynamic Routes
```tsx
// app/blog/[slug]/page.tsx
export default function Post({ params }: { params: { slug: string } }) {
return <h1>{params.slug}</h1>
}
// Generate static routes at build time
export async function generateStaticParams() {
const posts = await fetchAllPosts()
return posts.map(p => ({ slug: p.slug }))
}
```
## Catch-All & Optional Catch-All
```
app/shop/[...categories]/page.tsx → /shop/a/b/c
app/shop/[[...categories]]/page.tsx → /shop AND /shop/a/b/c
```
## Parallel Routes (Advanced)
```
app/
@team/page.tsx
@analytics/page.tsx
layout.tsx ← receives { team, analytics } props
```
```tsx
// layout.tsx
export default function Layout({ children, team, analytics }) {
return (
<div>
{children}
<aside>{team}</aside>
<aside>{analytics}</aside>
</div>
)
}
```
## Intercepting Routes
```
app/
feed/page.tsx
(..)photo/[id]/page.tsx ← intercepts /photo/[id] when navigated from /feed
photo/[id]/page.tsx ← full page on direct URL visit
```
Useful for modals that maintain background context.
## Middleware
```ts
// middleware.ts (project root)
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
const token = request.cookies.get("token")
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ["/dashboard/:path*", "/api/protected/:path*"],
}
```
## API Routes (Route Handlers)
```ts
// app/api/posts/route.ts
import { NextRequest, NextResponse } from "next/server"
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const page = searchParams.get("page") ?? "1"
const posts = await fetchPosts(parseInt(page))
return NextResponse.json({ posts })
}
export async function POST(request: NextRequest) {
const body = await request.json()
const post = await createPost(body)
return NextResponse.json(post, { status: 201 })
}
```
Use this skill when you need to plan a trip: build day-by-day itineraries, recommend accommodations and restaurants, estimate budgets, handle visa info, and...
---
name: travel-planner-cn
description: >
Use this skill when you need to plan a trip: build day-by-day itineraries,
recommend accommodations and restaurants, estimate budgets, handle visa
info, and generate packing lists. Optimized for Chinese travelers planning
domestic or international trips.
---
# 旅行攻略规划助手
## 核心工作流
### Step 1 — 收集旅行需求
必要信息:
- 目的地(城市/国家)
- 出发城市
- 旅行天数
- 出行人数和构成(独行/情侣/家庭/朋友团)
- 大致预算(人均)
- 旅行风格(文化探索/自然户外/美食打卡/购物休闲/混合)
- 特殊需求(无障碍/带宠物/素食等)
### Step 2 — 目的地概览
输出结构:
```
【目的地快照】
- 最佳旅行季节:
- 推荐旅行时长:
- 时区 / 货币 / 语言:
- 签证要求(中国护照):
- 当地交通方式:
- 安全提示:
```
### Step 3 — 行程框架设计
原则:
- 前1/3:适应 + 地标打卡(缓冲时间充足)
- 中间:深度体验(核心活动集中)
- 后1/3:收尾 + 购物/特色体验
每日行程模板:
```
Day N — [主题]
上午(09:00-12:00):
- 景点A(游览时长 + 门票)
- 步行/交通到景点B
午餐(12:00-13:30):
- 推荐餐厅:名称 | 特色菜 | 人均价格
下午(13:30-18:00):
- 景点C
- 景点D(可选,若有余力)
晚餐(18:30-20:00):
- 推荐餐厅或夜市
晚上(可选):
- 夜景/酒吧/演出推荐
💡 今日贴士:[当地小技巧/注意事项]
```
### Step 4 — 住宿推荐策略
按预算分级:
| 级别 | 人均/晚 | 推荐类型 |
|------|---------|----------|
| 经济 | <200元/¥30 | 青旅、民宿、经济型连锁 |
| 舒适 | 200-600元 | 精品酒店、连锁商务酒店 |
| 豪华 | >600元 | 五星酒店、度假村 |
选址建议:
- 优先选在地铁/主干道附近
- 第一晚订市中心,降低落地适应成本
- 多城市行程:每段住宿分散预订
### Step 5 — 预算估算
```
【人均预算估算】
交通(往返机票/高铁):¥____
当地交通(地铁/打车/包车):¥____/天 × N天
住宿:¥____/晚 × N晚
餐饮:¥____/天 × N天
景点门票:¥____(汇总)
购物/其他:¥____(预估)
─────────────────
合计:¥____ (人均)
建议备用金:预算 × 20%
```
### Step 6 — 实用清单生成
#### 证件与支付
- [ ] 护照(有效期6个月以上)
- [ ] 签证(如需)
- [ ] 国际驾照(如需租车)
- [ ] 外币现金 / 海外信用卡
- [ ] 旅行险(出发前购买)
#### 电子设备
- [ ] 转换插头(目的地插座类型)
- [ ] 移动电源(不超过100Wh)
- [ ] 手机卡/随身WiFi
- [ ] 离线地图(Google Maps / Maps.me)
#### 行程备份
- [ ] 酒店订单截图(离线可查)
- [ ] 紧急联系:当地大使馆电话
- [ ] 行程单发给家人/朋友
### Step 7 — 输出格式
```markdown
# {目的地} {N}日游攻略
## 行程概览
## Day 1 — 抵达 + 市中心探索
## Day 2 — ...
## 住宿推荐
## 预算估算
## 实用 Tips
## 打包清单
```
## 质量检查
- [ ] 行程节奏合理(每天不超过3-4个主要景点)
- [ ] 交通衔接说明清晰
- [ ] 有备选方案(雨天/疲惫时)
- [ ] 预算细化到每日
- [ ] 包含当地文化礼仪提示
FILE:references/budget-tips.md
# 旅行预算规划与省钱技巧
## 机票预订策略
### 最佳购票时机
| 航线类型 | 提前购票时间 | 建议 |
|---------|------------|------|
| 国内热门航线 | 21-45天 | 避开节假日前后 |
| 国内冷门航线 | 7-14天 | 有时临时价格更好 |
| 国际短途(东南亚/日韩) | 30-60天 | 关注促销日 |
| 国际长途(欧美澳) | 60-120天 | 旺季提前3-6月 |
### 省钱技巧
1. **日期灵活**:用"最低票价"日历视图选最便宜日期
2. **中转航班**:比直飞便宜20-40%,适合时间充裕者
3. **拼接航班**:不同航司分段购买(注意行李中转风险)
4. **里程兑换**:积累信用卡/航空里程,换商务舱或免费机票
5. **机场选择**:对比出发/到达不同机场(如北京首都 vs 大兴)
---
## 住宿省钱方法
### 住宿类型性价比比较
| 类型 | 性价比 | 适合人群 |
|------|--------|----------|
| 青年旅社(多人间) | ⭐⭐⭐⭐⭐ | 背包客/独行侠 |
| 青年旅社(私人间) | ⭐⭐⭐⭐ | 预算有限但要私密性 |
| Airbnb民宿 | ⭐⭐⭐⭐ | 3人以上团体 |
| 经济型连锁酒店 | ⭐⭐⭐ | 商务/安全优先 |
| 精品酒店 | ⭐⭐ | 有特殊体验需求 |
| 五星酒店 | ⭐ | 顶级体验 |
### 住宿预订技巧
- **提前预订**:热门目的地旺季提前2-4个月预订
- **会员积分**:充分利用携程/Marriott等积分系统
- **非中心区**:距离地铁5分钟以内的非市中心位置,价格差50%
- **周中入住**:周一至周四通常比周末便宜20-30%
- **灵活取消**:选可免费取消的房型(应对行程变化)
---
## 餐饮预算控制
### 按每日餐饮预算分配
```
经济型(¥100-150/天):
早餐:当地早市/便利店 ¥10-15
午餐:平价本地餐厅/快餐 ¥30-50
晚餐:中等餐厅 ¥60-80
零食/饮料 ¥10-20
舒适型(¥200-300/天):
早餐:酒店早餐或知名早点铺 ¥30-50
午餐:网红/推荐餐厅 ¥60-80
晚餐:特色餐厅/探店 ¥100-150
豪华型(¥400+/天):
可包含高端餐厅、下午茶、品酒等体验
```
### 境外餐饮省钱
- **超市 + 便利店**:早餐/午餐从超市解决,晚餐吃一顿好的
- **当地人推荐**:避开游客区,比旅游区便宜30-50%
- **午市套餐**:很多高档餐厅午市比晚市便宜40%
- **市场/街边摊**:东南亚街头小吃,¥10-20一顿
---
## 景点门票省钱
### 优惠购票渠道
1. **提前网上购票**:通常比现场便宜10-20%(支付宝/微信小程序)
2. **城市套票**:多景点打包(如新加坡City Pass)
3. **博物馆免费日**:很多博物馆有周二/周三免费或优惠
4. **学生证**:国际学生证(ISIC)在欧洲大量景点可享半价
5. **年票**:长期居住或多次拜访同城,年票通常3个月回本
### 景点排队节省时间(等于省钱)
- 提前预订指定入场时间
- 开门前20分钟到达(人最少)
- 工作日避开周末
- 使用快速通道(Klook等平台有售)
---
## 旅行保险性价比选择
### 必买条件
- 出境游:100%建议购买
- 参与高风险活动(滑雪/潜水/跳伞):确保保险涵盖
### 推荐保额
| 类目 | 建议保额 |
|------|---------|
| 境外医疗 | ≥ 100万元 |
| 紧急医疗撤离 | ≥ 50万元 |
| 行程取消/延误 | 实际花费的100% |
| 行李损失 | ≥ 1万元 |
### 购买渠道
- **支付宝/微信**:方便快捷,价格透明
- **信用卡附赠**:部分高端信用卡含旅行险(确认条款)
- **专业保险公司**:平安/太平洋等,适合长期旅行
- **避免**:机场销售的保险(价格贵,性价比低)
FILE:references/destinations-guide.md
# 旅行目的地快速参考指南
## 热门境内目的地
### 云南(昆明/大理/丽江/西双版纳)
- **最佳季节**:全年,3-5月/9-11月最佳(避开雨季)
- **推荐时长**:8-12天(全线),5天(单城市)
- **特色体验**:古城漫步、雪山徒步、热带雨林、少数民族文化
- **人均预算**:500-800元/天(含住宿餐饮交通)
- **交通**:飞昆明再转大巴/高铁/飞机至各地
### 西藏(拉萨/日喀则/林芝)
- **最佳季节**:4-10月(避冬季)
- **推荐时长**:7-14天
- **特色体验**:布达拉宫、纳木错、珠峰大本营、林芝桃花
- **注意事项**:需办入藏证,高原反应预防,提前适应1-2天
- **人均预算**:800-1500元/天(含特殊许可证费用)
### 三亚
- **最佳季节**:11月-次年4月(避台风季)
- **推荐时长**:4-6天
- **特色体验**:亚龙湾海滩、天涯海角、热带水果、潜水
- **人均预算**:600-1200元/天
### 成都
- **最佳季节**:春秋(3-5月,9-11月)
- **推荐时长**:3-5天
- **特色体验**:熊猫基地、宽窄巷子、川菜火锅、乐山大佛
- **人均预算**:400-700元/天
---
## 热门境外目的地
### 日本(东京/大阪/京都)
- **签证**:免签/落地签(2024年已恢复对中国公民的便利政策,以实际为准)
- **最佳季节**:3-4月(樱花),11月(红叶),12月(雪景)
- **汇率参考**:1元 ≈ 20日元(波动较大,出发前查询)
- **必备**:Suica/Pasmo交通卡,JR Pass(多城移动推荐)
- **人均预算**:600-1000元/天
- **必去**:浅草寺、新宿、大阪道顿堀、京都岚山/伏见稻荷
### 泰国(曼谷/清迈/普吉岛/芭提雅)
- **签证**:落地签或电子签(费用约400泰铢)
- **最佳季节**:11月-次年3月(凉季)
- **汇率参考**:1元 ≈ 5泰铢
- **必备**:防蚊喷雾,防晒,轻薄衣物
- **人均预算**:350-600元/天
- **注意**:寺庙需盖腿,骑象有争议(建议选友好营地)
### 新加坡
- **签证**:免签(中国护照)
- **最佳季节**:全年(避12月-1月雨季)
- **语言**:英语为主,普通话也广泛使用
- **必去**:滨海湾花园、克拉码头、环球影城、牛车水
- **人均预算**:800-1500元/天(消费较高)
- **交通**:MRT地铁非常便利
### 越南(河内/胡志明/岘港/河龙湾)
- **签证**:90天内免签(中国护照)
- **最佳季节**:北部:10-4月;南部:全年,11-4月最佳
- **人均预算**:300-500元/天(高性价比)
- **必备**:Grab App(叫车/外卖),VPN(访问Google)
- **特色**:法棍、越南咖啡、会安古镇、下龙湾游船
---
## 实用工具推荐
### 预订平台
| 类别 | 国内 | 国际 |
|------|------|------|
| 机票 | 携程、去哪儿、飞猪 | Google Flights、Skyscanner |
| 酒店 | 携程、美团 | Booking.com、Airbnb |
| 景点门票 | 同程旅行、抖音生活服务 | Klook、GetYourGuide |
### 导航与地图
- **国内**:高德地图(最准确)
- **境外**:Google Maps(需科学上网)、Maps.me(离线)
- **日本**:Google Maps + 乘换案内
### 货币兑换建议
1. 出发前兑换少量现金(应急用)
2. 当地ATM取款(通常汇率较好,注意手续费)
3. 信用卡刷卡(VISA/Mastercard全球覆盖)
4. 建议备份两张卡(避免单卡被吞)
### 旅行保险推荐要素
- 医疗救援(境外建议 ≥100万)
- 行程延误/取消
- 行李丢失/损坏
- 紧急撤离
- 推荐:国内平台(支付宝/微信)购买,比机场保险性价比高
FILE:references/itinerary-templates.md
# 旅行行程模板库
## 模板1:城市文化深度游(4天3晚)
适合:北京/西安/南京/成都等历史文化城市
```markdown
Day 1 — 抵达 + 市中心适应
上午:抵达,酒店办理入住,休息恢复
下午:步行探索酒店周边,找感觉
晚餐:推荐当地最具代表性的一道菜
晚上:逛夜市/步行街(轻松开始)
Day 2 — 核心景区
上午(09:00):头号景点(地标必打卡)
午餐:景区附近推荐餐厅
下午(14:00):第二景点(配套文化体验)
下午(16:30):休闲漫步/咖啡
晚餐:当地特色老字号
晚上:夜景打卡点
Day 3 — 小众 + 深度
上午:小众博物馆/历史街区
午餐:当地人气早午餐
下午:手工艺/烹饪体验课
下午(17:00):购物/纪念品
晚餐:餐饮聚集区探店
Day 4 — 出发前半天
上午:早市/早茶(最地道体验)
中午前:前往机场/车站
```
---
## 模板2:海岛休闲游(5天4晚)
适合:三亚/普吉岛/巴厘岛/马尔代夫等
```markdown
Day 1 — 抵达 + 酒店放松
- 下午抵达,办理入住
- 在酒店泳池/海滩放松
- 酒店餐厅晚餐(避免第一晚外出奔波)
Day 2 — 海上活动日
上午:浮潜/潜水(提前预订)
午餐:海鲜餐厅
下午:海滩自由玩耍/冲浪体验
晚餐:夜市/海鲜排档
晚上:海边酒吧/音乐现场
Day 3 — 岛屿探索日
上午:出海游(当地最美岛屿)
午餐:船上简餐或小岛午餐
下午:继续游岛/回程休息
下午(16:00):SPA 按摩(岛上必体验)
晚餐:度假村精致晚餐
Day 4 — 文化体验日
上午:参观当地寺庙/村落(文化游)
午餐:当地市场小吃
下午:瑜伽课/冥想体验(海岛特色)
晚餐:日落晚餐(提前预订海景位)
Day 5 — 早餐 + 购物 + 离开
上午:悠闲早餐
上午(10:00):购买当地特产/纪念品
中午:前往机场
```
---
## 模板3:欧洲多城市行程(10天)
```markdown
行程规划原则:
- 相邻城市间优先高铁(性价比高,市区到市区)
- 住宿选老城区(步行范围广,体验最佳)
- 热门博物馆提前至少2周预订(卢浮宫/梵蒂冈等)
Day 1-3:巴黎(法国)
Day 4-6:阿姆斯特丹(荷兰,火车约3.5h)
Day 7-8:布鲁日(比利时,火车约2h)
Day 9-10:布鲁塞尔(火车约1h,飞回)
每城市核心景点:
巴黎:埃菲尔铁塔/卢浮宫/蒙马特/塞纳河游船
阿姆斯特丹:运河游/梵高博物馆/Anne Frank故居/国立博物馆
布鲁日:中世纪古城步行/运河/巧克力/啤酒
布鲁塞尔:大广场/原子球/比利时华夫饼/跳蚤市场
```
---
## 常见行程问题解答
### Q:每天安排几个景点合适?
- 一般:2-3个主要景点(包含交通时间)
- 博物馆等需深度参观:1-2个
- 步行城市(古镇、老街):可放松多走几处
- 原则:宁少勿多,留白时间也是旅行的一部分
### Q:如何处理雨天备选?
规划时准备两类景点:
- **必须晴天**:户外景区、海滩、徒步
- **任何天气**:博物馆、购物中心、室内美食体验
提前查询天气,灵活调换顺序
### Q:家庭游(带老人/儿童)特别注意?
- 每天行程减少30%,多留休息时间
- 优先选地铁/方便停车的景点
- 备餐厅时确认儿童椅/老人友好
- 景点提前确认无障碍设施
- 安排1个"自由日"(老人休息,儿童玩乐设施)
### Q:如何防止行程过于赶?
- 两个景点之间交通时间算进去(至少30分钟)
- 下午安排比上午轻松(避免午后体力低)
- 每天保留1小时"自由时间"(随机探索)
- 最后一天不安排重大景点(预留购物/拖延)
Use this skill when you need to write, review, optimize, or debug SQL queries. Covers query construction, performance tuning, index strategy, window function...
---
name: sql-assistant
description: >
Use this skill when you need to write, review, optimize, or debug SQL
queries. Covers query construction, performance tuning, index strategy,
window functions, CTEs, and common anti-patterns for PostgreSQL, MySQL,
and SQLite.
---
# SQL 查询优化助手
## 核心工作流
### Step 1 — 理解需求
收集上下文:
- 数据库类型(PostgreSQL / MySQL / SQLite / SQL Server)
- 表结构(DDL 或列描述)
- 业务目标(查什么、过滤条件、聚合逻辑)
- 数据量级(小表 <10万 / 中表 <1000万 / 大表 >1000万)
- 性能问题描述(慢查询?错误结果?)
### Step 2 — 查询构建
#### 基础查询框架
```sql
SELECT
col1,
col2,
agg_func(col3) AS alias
FROM table_name t
JOIN other_table o ON t.id = o.fk_id
WHERE condition
GROUP BY col1, col2
HAVING agg_condition
ORDER BY alias DESC
LIMIT 100;
```
#### CTE 模式(复杂逻辑拆分)
```sql
WITH base_data AS (
SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY user_id
),
ranked AS (
SELECT *, RANK() OVER (ORDER BY order_count DESC) AS rk
FROM base_data
)
SELECT * FROM ranked WHERE rk <= 10;
```
### Step 3 — 性能优化策略
#### 索引策略
```sql
-- 单列索引
CREATE INDEX idx_orders_user ON orders(user_id);
-- 复合索引(遵循最左前缀原则)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- 覆盖索引(避免回表)
CREATE INDEX idx_orders_cover ON orders(user_id, created_at, status, amount);
```
#### EXPLAIN 分析
```sql
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)
SELECT * FROM orders WHERE user_id = 42;
```
关注指标:
- `Seq Scan` → 考虑加索引
- `rows` 估算偏差大 → 需要 ANALYZE
- `cost` 高 → 优化 JOIN 顺序或添加索引
- `Buffers: shared hit/read` → 缓存命中率
### Step 4 — 常见优化模式
#### 分页优化(大表 OFFSET 慢)
```sql
-- ❌ 慢:OFFSET 需扫描丢弃前N行
SELECT * FROM orders ORDER BY id LIMIT 20 OFFSET 100000;
-- ✅ 快:游标分页
SELECT * FROM orders WHERE id > :last_seen_id ORDER BY id LIMIT 20;
```
#### IN 子查询优化
```sql
-- ❌ 可能慢
SELECT * FROM users WHERE id IN (SELECT user_id FROM premium_members);
-- ✅ 用 EXISTS 或 JOIN
SELECT u.* FROM users u
JOIN premium_members pm ON u.id = pm.user_id;
```
#### 避免函数破坏索引
```sql
-- ❌ 函数包装列,索引失效
WHERE YEAR(created_at) = 2026
-- ✅ 范围条件,索引有效
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'
```
### Step 5 — 窗口函数常用模式
```sql
-- 分组内排名
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC)
-- 累计求和
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at)
-- 环比计算
LAG(revenue, 1) OVER (ORDER BY month) AS prev_month_revenue
-- 移动平均
AVG(score) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
```
### Step 6 — 查询审查清单
- [ ] SELECT 只取需要的列(避免 SELECT *)
- [ ] WHERE 条件列有索引
- [ ] JOIN 条件有索引
- [ ] 大表分页用游标而非 OFFSET
- [ ] 聚合前先 WHERE 过滤(减少聚合数据量)
- [ ] 复杂逻辑用 CTE 而非嵌套子查询
- [ ] 无 N+1 查询问题
## 反模式速查
| 反模式 | 修复方式 |
|--------|----------|
| SELECT * | 显式列出需要的列 |
| OFFSET 大分页 | 改用游标/keyset 分页 |
| WHERE 列用函数 | 改用范围条件 |
| 隐式类型转换 | 确保参数类型匹配 |
| 无 LIMIT 的全表扫描 | 加 LIMIT 或索引过滤 |
| OR 替代 UNION | 改用 UNION ALL |
FILE:references/database-features.md
# SQL 数据库特性对比与最佳实践
## PostgreSQL vs MySQL vs SQLite 特性对比
| 特性 | PostgreSQL | MySQL 8+ | SQLite |
|------|-----------|----------|--------|
| 窗口函数 | ✅ 完整支持 | ✅ 完整支持 | ✅ 3.25+ |
| CTE (WITH) | ✅ 递归CTE | ✅ | ✅ |
| JSON 支持 | ✅ jsonb(高效) | ✅ | 有限 |
| 全文搜索 | ✅ 内置 | ✅ | 有限 |
| 并发控制 | MVCC(优秀) | MVCC | WAL模式 |
| 最大数据库大小 | 无限制 | 无限制 | 281TB |
| 适用场景 | 复杂查询/分析 | Web应用/OLTP | 嵌入式/移动端 |
---
## PostgreSQL 专属特性
### JSONB 操作
```sql
-- 创建带 JSONB 列的表
CREATE TABLE products (id SERIAL, attrs JSONB);
-- 查询 JSONB 字段
SELECT * FROM products WHERE attrs->>'color' = 'red';
SELECT * FROM products WHERE attrs @> '{"color": "red"}';
-- JSONB 聚合
SELECT attrs->>'category', COUNT(*) FROM products GROUP BY attrs->>'category';
-- 创建 JSONB 索引
CREATE INDEX idx_products_attrs ON products USING GIN(attrs);
```
### 数组类型
```sql
-- 数组列
CREATE TABLE users (tags TEXT[]);
INSERT INTO users VALUES (ARRAY['dev', 'python', 'sql']);
-- 数组查询(包含)
SELECT * FROM users WHERE 'python' = ANY(tags);
SELECT * FROM users WHERE tags @> ARRAY['dev', 'python'];
-- 数组展开
SELECT UNNEST(tags) AS tag FROM users;
```
### 全文搜索
```sql
-- 创建全文索引
CREATE INDEX idx_articles_fts ON articles USING GIN(to_tsvector('english', content));
-- 全文搜索查询
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ plainto_tsquery('english', 'database optimization');
```
---
## MySQL 专属优化
### InnoDB 配置优化
```sql
-- 查看缓冲池大小(建议设为物理内存的70-80%)
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
-- 查看慢查询
SHOW VARIABLES LIKE 'slow_query_log%';
SHOW VARIABLES LIKE 'long_query_time';
```
### MySQL 特有 JSON 函数
```sql
-- 提取 JSON 值
SELECT JSON_EXTRACT(config, '$.theme') FROM settings;
SELECT config->>'$.theme' FROM settings; -- 简写
-- JSON 数组操作
SELECT JSON_ARRAYAGG(name) FROM users WHERE active = 1;
SELECT JSON_OBJECTAGG(id, name) FROM users;
```
### 分区表(大表优化)
```sql
-- 按日期范围分区
CREATE TABLE orders (
id BIGINT,
created_at DATE,
...
) PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION p2026 VALUES LESS THAN (2027)
);
```
---
## 事务与并发控制
### 事务隔离级别
| 级别 | 脏读 | 不可重复读 | 幻读 | 适用场景 |
|------|------|-----------|------|----------|
| READ UNCOMMITTED | ✅ | ✅ | ✅ | 极少使用 |
| READ COMMITTED | ❌ | ✅ | ✅ | 多数OLTP |
| REPEATABLE READ | ❌ | ❌ | ✅(MySQL) | MySQL默认 |
| SERIALIZABLE | ❌ | ❌ | ❌ | 高一致性需求 |
PostgreSQL 默认:READ COMMITTED
MySQL InnoDB 默认:REPEATABLE READ
### 乐观锁 vs 悲观锁
```sql
-- 悲观锁(SELECT FOR UPDATE)
BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- 乐观锁(版本号)
UPDATE accounts
SET balance = balance - 100, version = version + 1
WHERE id = 1 AND version = :expected_version;
-- 检查 affected rows = 1,否则重试
```
---
## 数据库设计规范
### 表设计规范
1. 主键:推荐使用 BIGINT SERIAL/AUTO_INCREMENT(非 UUID,避免索引碎片)
2. 时间戳:使用 `created_at TIMESTAMPTZ DEFAULT NOW()` 和 `updated_at`
3. 软删除:添加 `deleted_at TIMESTAMPTZ NULL`(而非物理删除)
4. 字符集:UTF8MB4(MySQL),UTF8(PostgreSQL)
### 命名规范
- 表名:蛇形命名,复数(`user_orders`)
- 列名:蛇形命名(`created_at`)
- 索引:`idx_{表名}_{列名}` 或 `idx_{表名}_{列名}_{列名}`
- 外键:`fk_{子表}_{父表}`
### 必备字段模板
```sql
CREATE TABLE example (
id BIGSERIAL PRIMARY KEY,
-- 业务字段...
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ NULL -- 软删除
);
-- 自动更新 updated_at(PostgreSQL)
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_update_updated_at
BEFORE UPDATE ON example
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
```
FILE:references/query-optimization.md
# SQL 性能调优完整指南
## 查询执行计划解读
### PostgreSQL EXPLAIN 输出解读
```sql
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 1;
```
输出示例:
```
Index Scan using idx_orders_user on orders
(cost=0.43..8.45 rows=1 width=64)
(actual time=0.021..0.023 rows=1 loops=1)
Index Cond: (user_id = 1)
Buffers: shared hit=3
Planning Time: 0.5 ms
Execution Time: 0.05 ms
```
关键字段说明:
| 字段 | 含义 | 优化方向 |
|------|------|----------|
| Seq Scan | 全表扫描 | 添加索引 |
| Index Scan | 索引扫描(回表) | 考虑覆盖索引 |
| Index Only Scan | 仅索引(最优) | 目标状态 |
| Hash Join | 哈希连接 | 大表连接正常 |
| Nested Loop | 嵌套循环 | 小表驱动大表时有效 |
| rows(估算vs实际差异大) | 统计信息不准 | 运行 ANALYZE |
### MySQL EXPLAIN 关键字段
```sql
EXPLAIN SELECT * FROM orders WHERE user_id = 1\G
```
| Extra 字段 | 含义 |
|-----------|------|
| Using index | 覆盖索引,最优 |
| Using where | 过滤条件在存储引擎层 |
| Using filesort | 内存/磁盘排序,考虑索引 |
| Using temporary | 使用临时表,性能警告 |
| NULL | 正常全表扫描 |
---
## 索引设计原则
### 索引选择性
- 选择性 = 不同值数量 / 总行数
- 选择性 > 0.1 的列适合建索引
- 性别、状态等低选择性列不适合单独建索引
### 复合索引最左前缀规则
```sql
-- 索引:(a, b, c)
SELECT * FROM t WHERE a = 1; -- ✅ 用到索引
SELECT * FROM t WHERE a = 1 AND b = 2; -- ✅ 用到索引
SELECT * FROM t WHERE b = 2; -- ❌ 不用索引
SELECT * FROM t WHERE a = 1 AND c = 3; -- ✅ 仅用到 a 部分
SELECT * FROM t WHERE a > 1 AND b = 2; -- ✅ a范围查询,b可能用不到
```
### 索引设计口诀
1. **等值查询**列放前面
2. **范围查询**列放后面
3. **ORDER BY**列放最后(与查询顺序一致)
4. **高选择性**列优先
---
## 慢查询诊断流程
### Step 1:开启慢查询日志
```sql
-- MySQL
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒记录
-- PostgreSQL (postgresql.conf)
log_min_duration_statement = 1000 -- 毫秒
```
### Step 2:分析慢查询
```bash
# MySQL 慢查询分析工具
pt-query-digest /var/log/mysql/slow.log
# 或用 mysqldumpslow
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
```
### Step 3:EXPLAIN 分析
```sql
EXPLAIN FORMAT=JSON SELECT ...\G -- MySQL
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT ...; -- PostgreSQL
```
### Step 4:优化并验证
- 添加索引 → 再次 EXPLAIN 确认使用索引
- 重写查询 → 对比执行时间
- 更新统计信息:`ANALYZE TABLE orders;`(MySQL)或 `ANALYZE orders;`(PG)
---
## 常见业务场景 SQL 模式
### 分组取最新记录(每用户最后一条订单)
```sql
-- 方法1:窗口函数(推荐)
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) rn
FROM orders
) t WHERE rn = 1;
-- 方法2:关联子查询
SELECT o.* FROM orders o
WHERE o.created_at = (
SELECT MAX(created_at) FROM orders WHERE user_id = o.user_id
);
```
### 统计连续登录天数
```sql
WITH login_gaps AS (
SELECT
user_id,
login_date,
login_date - INTERVAL (ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date)) DAY AS grp
FROM user_logins
),
streaks AS (
SELECT user_id, grp, COUNT(*) AS consecutive_days
FROM login_gaps
GROUP BY user_id, grp
)
SELECT user_id, MAX(consecutive_days) AS max_streak
FROM streaks
GROUP BY user_id;
```
### 同比环比计算
```sql
SELECT
month,
revenue,
LAG(revenue, 1) OVER (ORDER BY month) AS prev_month,
ROUND((revenue - LAG(revenue, 1) OVER (ORDER BY month)) * 100.0
/ LAG(revenue, 1) OVER (ORDER BY month), 2) AS mom_growth_pct,
LAG(revenue, 12) OVER (ORDER BY month) AS same_month_last_year,
ROUND((revenue - LAG(revenue, 12) OVER (ORDER BY month)) * 100.0
/ LAG(revenue, 12) OVER (ORDER BY month), 2) AS yoy_growth_pct
FROM monthly_revenue
ORDER BY month;
```
Use this skill when you need to write viral Xiaohongshu (小红书) posts, product notes, travel diaries, lifestyle content, or any copy optimized for the RED plat...
--- name: xiaohongshu-writer description: > Use this skill when you need to write viral Xiaohongshu (小红书) posts, product notes, travel diaries, lifestyle content, or any copy optimized for the RED platform. Generates hook titles, structured body copy, emoji decoration, and hashtag sets that match platform algorithms. --- # 小红书爆款文案写作助手 ## 核心工作流 ### Step 1 — 理解选题 收集用户输入: - 内容类型(种草/测评/教程/日记/攻略) - 目标受众(年龄、兴趣、消费层次) - 核心卖点或故事核心(1-2句话) ### Step 2 — 标题创作(Hook Title) 遵循小红书标题公式: | 公式 | 示例 | |------|------| | 数字+利益 | 「5个让皮肤亮起来的早C晚A步骤」 | | 痛点+解决方案 | 「熬夜党救星!黑眼圈消失的秘密」 | | 反差/惊喜 | 「花了20元,却拍出大牌感的方法」 | | 疑问/好奇 | 「为什么博主都在用这个平价替代品?」 | 规则: - 长度 18-22 字 - 含 1-2 个 emoji - 含关键词(利于搜索) - 制造悬念或强利益点 ### Step 3 — 正文结构 ``` [开头钩子] 2-3行,引发共鸣或好奇 [分隔符] —————————————— [正文主体] - 分段清晰,每段 3-5 行 - 加小标题(emoji + 关键词) - 口语化,有温度,像朋友分享 - 具体细节优于泛泛而谈 [结尾行动号召] 引导互动(点赞/收藏/评论) ``` ### Step 4 — Emoji 装饰 - 开头用强调 emoji:✨🌟💫🔥👇 - 段落标题用场景 emoji:🌿🎀💄👗✈️🍜 - 结尾互动 emoji:💕🫶👍📌 ### Step 5 — Hashtag 策略 ``` 大标签(>500万): #穿搭 #护肤 #探店 中标签(50-500万): #平价好物推荐 #职场穿搭 小标签(<50万): #2026春季穿搭 #北京探店打卡 ``` 每篇建议:3大 + 4中 + 3小 = 共10个话题标签 ### Step 6 — 内容类型模板 #### 种草文 - 开头:「真的被种草了!」/ 「发现宝藏了」 - 核心:外观描述 → 使用感受 → 对比同类 → 价格锚定 - 结尾:「这个不买真的会后悔!」 #### 教程文 - 开头:步骤数字钩子 - 核心:步骤清单(带序号emoji ①②③) - 结尾:鼓励尝试 + 提问互动 #### 测评文 - 开头:产品期待值设定 - 核心:优点(2-3条)→ 不足(1条)→ 适合人群 - 结尾:推荐指数(⭐x5) ## 质量检查清单 - [ ] 标题有 hook,含 emoji - [ ] 正文分段,无大段文字 - [ ] 口语化,有个人温度 - [ ] 包含具体细节(价格/地点/品牌) - [ ] 10个话题标签(大中小搭配) - [ ] 结尾引导互动 ## 示例输出结构 ``` [标题] ✨ 发现了让妆容撑一整天的神仙底妆!附步骤 正文: 姐妹们我真的找到宝藏了🥹 这个底妆方法我已经用了3个月... 🌟 Step 1 - 打底关键 ... 👇 手把手教你复制这个妆容 ... #底妆技巧 #持妆方法 #彩妆教程 ... ``` FILE:references/content-templates.md # 小红书内容模板库 ## 模板 1:美妆护肤种草 ``` ✨ 真的,这个[产品名]让我回购了3次 用了两个月终于可以来说说了😭 [痛点描述:原来的困扰] 💄 开箱第一眼: [外包装/颜值描述] 🌿 上脸感受: [质地/气味/使用步骤] 📊 用了一个月后: [效果描述,要具体,比如"毛孔肉眼可见缩小了"] 💰 价格:XX元,[平台]有售 适合:[适合人群] 不适合:[注意事项] 姐妹们,这个真的值得!你们有用过吗? 评论区告诉我你的感受💕 #护肤好物 #[产品类型]推荐 #平价护肤 #[具体功效] #护肤日记 ``` ## 模板 2:旅行探店攻略 ``` 🗺️ 在[地名]发现了宝藏地!附详细攻略 终于打卡了心心念念的[景点/餐厅]!✈️ 分享给想去的姐妹们👇 📍 地址:[具体地址] 🕐 开放时间:[时间] 💰 人均:[价格] ⏱️ 建议游览时长:[时长] 🌟 亮点: ① [亮点1] ② [亮点2] ③ [亮点3] 💡 贴士: - [交通建议] - [最佳游览时间] - [必拍机位] ⚠️ 注意:[避坑提示] 收藏备用,下次你也去打卡! 你去过[地名]吗?有好推荐评论区见🫶 #[地名]旅游 #[城市]探店 #旅行攻略 #打卡地 #旅行日记 ``` ## 模板 3:穿搭教程 ``` 👗 [场景]穿搭公式,素人也能穿出[效果] 不会穿搭的姐妹看这里! 这套公式我已经用了半年🔥 🎯 核心公式: [颜色搭配规则] + [单品组合逻辑] 必备单品清单: ① [上衣]:[品牌/购入渠道] ¥XX ② [下装]:[品牌/购入渠道] ¥XX ③ [外套(可选)] ④ [鞋子]:[品牌/购入渠道] ¥XX ⑤ [配饰] ✨ 适合体型:[描述] ❌ 不适合:[描述] 总花费:¥XXX 照片里的效果真的绝! 你会get这套搭配吗? #穿搭分享 #[场景]穿搭 #[季节]穿搭 #平价穿搭 #穿搭教程 ``` ## 模板 4:美食探店 ``` 🍜 [区域]宝藏小店!排队1小时值得吗? 答案是:值!!! 今天带你们云打卡[餐厅名]🥢 📍 位置:[地址] | 地铁[X]号线[站]步行XX分钟 💰 人均:¥XX 🕐 营业时间:[时间] ⌛ 排队情况:工作日约XX分钟,周末约XX分钟 🌟 必点菜: 1⃣ [菜名] ¥XX — [描述,味道/特色] 2⃣ [菜名] ¥XX — [描述] 3⃣ [菜名] ¥XX — [描述] 可以跳过:[菜名](个人觉得一般) 💡 点餐技巧: [排队时间建议/点餐小窍门] 总体评价:味道⭐⭐⭐⭐ 性价比⭐⭐⭐⭐⭐ 环境⭐⭐⭐ 强烈推荐!你吃过吗? #探店 #[城市]美食 #[美食类型] #宝藏餐厅 #吃货打卡 ``` ## 高频爆款开头句式 - 「姐妹们!我发现了一个宝藏!」 - 「真的绷不住了,这个也太好用了!」 - 「后悔没早点知道这个!」 - 「花了X块,效果媲美XX倍价格的大牌」 - 「被安利了100次,终于用上了...」 - 「入坑XX年,这是我回购次数最多的」 - 「素人改造,亲测有效!」 FILE:references/hashtag-strategy.md # 话题标签策略与关键词库 ## 话题标签选择原则 ### 热度分级定义(2026参考) - **超级大标签** (>2000万笔记):流量大但竞争激烈,曝光概率低 - **大标签** (500-2000万):适合蹭流量,但需要强内容才能脱颖而出 - **中标签** (50-500万):黄金区间,有流量但竞争合理 - **小标签** (<50万):精准受众,转化率高,适合垂直内容 ### 推荐组合比例 - 超级大/大标签:2-3个(引流) - 中标签:3-4个(精准触达) - 小标签:3-4个(垂直深耕) --- ## 分类关键词库 ### 美妆护肤 大标签:#护肤 #彩妆 #口红 #粉底液 #护肤品 中标签:#护肤好物推荐 #平价护肤 #成分党护肤 #油皮护肤 小标签:#[品牌名]测评 #[产品名]使用感 #[功效]精华推荐 ### 穿搭时尚 大标签:#穿搭 #ootd #今日穿搭 中标签:#显瘦穿搭 #通勤穿搭 #法式穿搭 #韩系穿搭 小标签:#[场合]穿搭公式 #[季节][风格]穿搭 ### 美食探店 大标签:#美食 #探店 #好吃的 中标签:#[城市]探店 #[城市]美食推荐 #网红餐厅 小标签:#[区域]探店 #[菜系]推荐 #[餐厅名] ### 旅行出行 大标签:#旅行 #旅游 #打卡 中标签:#[目的地]旅游 #[目的地]攻略 #[出行方式]旅行 小标签:#[景点]打卡 #[目的地]自由行攻略 ### 生活好物 大标签:#好物推荐 #种草 #生活 中标签:#家居好物 #宿舍好物 #平价好物推荐 小标签:#[产品类别]推荐 #[使用场景]好物 ### 学习成长 大标签:#学习 #自律 #打卡 中标签:#学习方法 #效率工具 #考研备考 小标签:#[学科]学习方法 #[资格证]备考经验 --- ## 标题关键词高频词汇 ### 情绪触发词 - 惊喜类:宝藏、神仙、绝了、太好了、惊艳 - 紧迫类:必入、不能错过、后悔没早知道、救星 - 真实类:亲测、真实感受、用了N个月、回购N次 - 对比类:平价替代、媲美大牌、XX倍价格效果 ### 数字使用技巧 - 具体数字 > 模糊描述:「用了3个月」>「用了很久」 - 价格锚定:「XX元 vs XX元」的对比 - 步骤数字:「5步学会」「3分钟教程」 - 数量清单:「10个必买」「7个宝藏」 ### SEO 优化关键词布局 1. 标题前10字:放核心关键词(产品名/地名/技能名) 2. 正文第1段:自然融入关键词1-2次 3. 段落小标题:使用长尾关键词 4. 话题标签:覆盖核心词的所有变体 --- ## 评论区互动话术 ### 引导评论的结尾句式 - 「你们有用过[产品]吗?评论区聊聊!」 - 「有想要的[教程/攻略]告诉我,下期安排!」 - 「这个[价格/效果]你们觉得值吗?」 - 「姐妹们都去打卡了[地点]吗?」 ### 回复评论技巧 - 在发布后1小时内积极回复评论(影响算法判断) - 回复要有实质内容,避免只回复「谢谢」 - 主动提问引导二次互动 FILE:references/platform-algorithm.md # 小红书平台算法与流量逻辑 ## 流量分发机制 小红书采用「兴趣图谱」推荐系统: 1. **冷启动**:新笔记先推给 200-500 人(兴趣标签匹配用户) 2. **互动率评估**:点赞率 / 收藏率 / 评论率 / 完读率 3. **流量扩散**:互动率达标 → 推给更大范围人群 4. **搜索流量**:标题 + 正文关键词影响 SEO 排名 ## 关键互动指标权重(估算) | 互动类型 | 权重 | 说明 | |----------|------|------| | 收藏 | ⭐⭐⭐⭐⭐ | 最高权重,表示内容有保存价值 | | 点赞 | ⭐⭐⭐⭐ | 高权重,快速信号 | | 评论 | ⭐⭐⭐⭐ | 高权重,尤其有实质内容的评论 | | 分享 | ⭐⭐⭐ | 中等权重 | | 关注 | ⭐⭐ | 较低即时权重,但影响长期粉丝 | ## 最佳发布时间 | 时间段 | 适合内容类型 | |--------|-------------| | 07:00-09:00 | 早间护肤/穿搭/早餐 | | 12:00-13:00 | 美食/探店/午间碎片 | | 18:00-20:00 | 下班休闲/购物种草 | | 21:00-23:00 | 深夜护肤/情感/学习 | ## 高互动内容特征分析 ### 种草文爆款要素 - **视觉钩子**:封面图高对比、文字清晰可读 - **价格锚点**:明确价格(尤其是「平价替代」概念) - **真实感**:使用前后对比、真实场景图 - **痛点切入**:开头直接戳中用户痛点 ### 教程文爆款要素 - **步骤清晰**:数字序号(①②③)一目了然 - **可复制性**:读者看完就能做 - **工具清单**:材料/工具列表具体可购买 - **效果展示**:最终成品图/视频 ### 测评文爆款要素 - **客观立场**:承认不足,增加可信度 - **横向对比**:与竞品比较(价格/成分/效果) - **使用周期**:用了多久,真实变化 ## SEO 关键词布局 - **标题**:核心关键词放在前10字 - **正文第一段**:重复1-2次核心词 - **段落小标题**:相关长尾词 - **话题标签**:关键词话题优先 ## 常见踩坑 ❌ 封面图模糊/暗淡 → 点击率低 ❌ 标题无利益点 → 点击率低 ❌ 正文大段文字不分行 → 完读率低 ❌ 全是硬广语气无真实感 → 互动率低 ❌ 话题标签选太冷门 → 搜索流量少 ❌ 发布时间不对 → 错过目标受众在线时段
中文前端开发面试辅导,支持分级练习、模拟问答、面试出题及详细解析,涵盖JS/TS/React/Vue等核心领域。
---
name: frontend-interviewer-cn
description: Chinese frontend developer interview coach and question bank. Use when Chinese-speaking frontend developers want to: (1) practice interview questions by difficulty level (junior/mid/senior/expert), (2) simulate mock interviews with follow-up questions and scoring, (3) prepare answers for JavaScript, TypeScript, React, Vue, CSS, browser internals, performance optimization, engineering, networking/security, or algorithms, (4) act as an interviewer to generate questions and evaluate candidates, or (5) get detailed answer explanations and best practices for any frontend topic.
---
# 前端面试官 (Frontend Interviewer CN)
## 概述
本 skill 为中文前端开发者提供系统化的面试备考与面试辅助服务,覆盖初级到专家级的全栈前端知识体系,支持候选人备考模式和面试官出题模式。
## 知识领域与题库索引
根据用户问题领域,按需读取对应参考文件:
| 领域 | 参考文件 | 核心话题 |
|------|----------|----------|
| JavaScript 核心 | `references/javascript.md` | 原型链、闭包、事件循环、Promise、ES6+ |
| TypeScript 进阶 | `references/typescript.md` | 泛型、类型体操、装饰器、工具类型 |
| React 深度 | `references/react.md` | Fiber、Diff 算法、Hooks、状态管理 |
| Vue 深度 | `references/vue.md` | 响应式原理、虚拟 DOM、Composition API |
| CSS 布局与动画 | `references/css.md` | Flexbox、Grid、BFC、动画性能 |
| 浏览器原理 | `references/browser.md` | 渲染流程、V8、内存管理、缓存 |
| 性能优化 | `references/performance.md` | 加载优化、运行时优化、监控指标 |
| 工程化 | `references/engineering.md` | Webpack/Vite、CI/CD、微前端、Monorepo |
| 网络与安全 | `references/network.md` | HTTP/2/3、CORS、XSS/CSRF、HTTPS |
| 算法与数据结构 | `references/algorithm.md` | 前端高频算法题、复杂度分析 |
**使用原则:** 只在用户明确询问某领域时才读取对应文件,避免一次性加载所有文件。
## 难度分级体系
```
初级 (Junior) ─ 0-1年经验,基础概念理解
中级 (Mid) ─ 1-3年经验,深入原理,实践经验
高级 (Senior) ─ 3-5年经验,系统设计,性能优化
专家 (Expert) ─ 5年+经验,架构设计,技术决策,团队影响力
```
## 工作模式
### 模式 1:候选人备考模式(默认)
用户想练习和备考时:
1. **确认目标** — 询问目标岗位级别、重点领域(若用户未说明)
2. **出题** — 根据难度和领域从题库选题,每次1-3道
3. **等待作答** — 不提前给出答案
4. **追问** — 对回答进行深入追问(至少1-2个追问)
5. **评分反馈** — 给出 1-10 分评分 + 优点 + 改进点 + 参考答案
**追问示例:**
- 用户回答了闭包概念 → "能说说闭包可能导致的内存泄漏吗?"
- 用户提到了 Diff 算法 → "React 16 之后 Diff 算法有什么变化?"
**评分维度:**
- 概念准确性(30%)
- 深度与细节(30%)
- 实际应用经验(25%)
- 表达清晰度(15%)
### 模式 2:面试官模式
用户需要出题或评估候选人时:
1. **需求确认** — 候选人级别、岗位方向、重点考察能力
2. **题目生成** — 生成面试题套卷(含主问题 + 预设追问 + 评分要点)
3. **候选人评估** — 分析候选人回答,给出录用建议和综合评价
**题目套卷格式:**
```
【主题】JavaScript 异步编程(中级)
[主问题] 请解释 Promise 和 async/await 的关系,以及各自的优缺点。
[预设追问1] Promise.all、Promise.race、Promise.allSettled 的区别?
[预设追问2] async 函数的错误处理最佳实践是什么?
[评分要点] 是否提到微任务队列、错误传播、并发控制...
```
### 模式 3:知识点速查
用户想快速了解某知识点时,直接给出:
- 核心概念简述(2-3句话)
- 关键要点列表
- 代码示例(如适用)
- 面试中常见考察角度
## 出题规范
### 好题的标准
- **有区分度** — 初级问"是什么",高级问"为什么",专家问"怎么设计"
- **联系实际** — 结合业务场景,而非纯粹考背诵
- **可深入** — 每道题都有可追问的层次
### 题目类型
- **概念题** — 解释原理(适合初/中级)
- **比较题** — A vs B(适合中/高级)
- **场景题** — 给定场景如何解决(适合高/专家级)
- **代码题** — 手写代码或代码 review(适合中级以上)
- **系统设计题** — 设计方案(适合专家级)
## 反馈格式规范
```
📊 评分:X/10
✅ 回答亮点:
- [具体指出好的地方]
🔍 深入追问:
- [1-2个追问]
📝 参考答案要点:
- [关键知识点]
- [代码示例(若适用)]
💡 延伸学习:
- [相关知识点推荐]
```
## 快速触发词
| 用户说 | 动作 |
|--------|------|
| "出几道JS题" / "考我JS" | 模式1,读 javascript.md,出3道中级题 |
| "模拟面试" / "开始面试" | 模式1,先询问目标级别和领域 |
| "帮我出一套面试题" | 模式2,确认候选人信息 |
| "解释一下XXX" | 模式3,直接知识点解析 |
| "XX和YY的区别" | 模式3,对比解析 |
| "我答XXX,帮我打分" | 模式1,评分+反馈 |
FILE:references/algorithm.md
# 前端算法高频题
## 初级
### 1. 手写防抖(debounce)和节流(throttle)
**考察点**:闭包、定时器、性能优化
```js
// 防抖:最后一次触发后 delay ms 执行
function debounce(fn, delay) {
let timer = null
return function (...args) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 节流:每 interval ms 最多执行一次
function throttle(fn, interval) {
let lastTime = 0
return function (...args) {
const now = Date.now()
if (now - lastTime >= interval) {
lastTime = now
fn.apply(this, args)
}
}
}
// 使用场景
const onSearch = debounce((val) => fetchSearch(val), 300) // 搜索输入
const onScroll = throttle(() => checkPosition(), 100) // 滚动监听
```
---
### 2. 数组去重(多种方式)
**考察点**:数据结构、ES6
```js
const arr = [1, 2, 2, 3, 3, 4]
// 方式1:Set(最简洁)
const unique1 = [...new Set(arr)]
// 方式2:filter + indexOf
const unique2 = arr.filter((v, i) => arr.indexOf(v) === i)
// 方式3:reduce
const unique3 = arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], [])
// 对象数组按属性去重
const users = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 1, name: 'A' }]
const uniqueUsers = [...new Map(users.map(u => [u.id, u])).values()]
```
---
### 3. 手写深拷贝
**考察点**:递归、数据类型判断
```js
function deepClone(target, map = new WeakMap()) {
// 处理基本类型和 null
if (target === null || typeof target !== 'object') return target
// 处理循环引用
if (map.has(target)) return map.get(target)
// 处理特殊对象
if (target instanceof Date) return new Date(target)
if (target instanceof RegExp) return new RegExp(target)
// 处理数组和普通对象
const clone = Array.isArray(target) ? [] : {}
map.set(target, clone)
for (const key of Reflect.ownKeys(target)) {
clone[key] = deepClone(target[key], map)
}
return clone
}
// 测试循环引用
const obj = { a: 1, b: { c: 2 } }
obj.self = obj
const cloned = deepClone(obj) // 不会栈溢出
```
---
## 中级
### 4. 实现 LRU 缓存
**考察点**:Map、数据结构设计
```js
class LRUCache {
constructor(capacity) {
this.capacity = capacity
this.cache = new Map() // Map 保持插入顺序
}
get(key) {
if (!this.cache.has(key)) return -1
// 移到最近使用:删除再重新插入
const val = this.cache.get(key)
this.cache.delete(key)
this.cache.set(key, val)
return val
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key)
} else if (this.cache.size >= this.capacity) {
// 删除最久未使用(Map 第一个元素)
this.cache.delete(this.cache.keys().next().value)
}
this.cache.set(key, value)
}
}
// 测试
const lru = new LRUCache(2)
lru.put(1, 1)
lru.put(2, 2)
lru.get(1) // 1,1 变为最近使用
lru.put(3, 3) // 淘汰 2
lru.get(2) // -1,已淘汰
```
---
### 5. 手写 Promise.all 和 Promise.race
**考察点**:Promise、异步编程
```js
// Promise.all:全部成功才 resolve,任一失败就 reject
function promiseAll(promises) {
return new Promise((resolve, reject) => {
const results = []
let count = 0
if (promises.length === 0) return resolve([])
promises.forEach((p, i) => {
Promise.resolve(p).then(val => {
results[i] = val
if (++count === promises.length) resolve(results)
}).catch(reject)
})
})
}
// Promise.race:第一个完成(无论成功失败)就结束
function promiseRace(promises) {
return new Promise((resolve, reject) => {
promises.forEach(p => Promise.resolve(p).then(resolve).catch(reject))
})
}
// Promise.allSettled:等全部完成,无论成败
function promiseAllSettled(promises) {
return Promise.all(promises.map(p =>
Promise.resolve(p)
.then(value => ({ status: 'fulfilled', value }))
.catch(reason => ({ status: 'rejected', reason }))
))
}
```
---
## 高级
### 6. 二叉树遍历(递归 + 迭代)
**考察点**:树、递归、栈
```js
// 前序遍历:根-左-右
function preorder(root) {
if (!root) return []
// 递归
return [root.val, ...preorder(root.left), ...preorder(root.right)]
// 迭代
const res = [], stack = [root]
while (stack.length) {
const node = stack.pop()
res.push(node.val)
if (node.right) stack.push(node.right) // 先压右
if (node.left) stack.push(node.left) // 再压左
}
return res
}
// 层序遍历(BFS)
function levelOrder(root) {
if (!root) return []
const res = [], queue = [root]
while (queue.length) {
const level = []
const size = queue.length
for (let i = 0; i < size; i++) {
const node = queue.shift()
level.push(node.val)
if (node.left) queue.push(node.left)
if (node.right) queue.push(node.right)
}
res.push(level)
}
return res
}
```
---
### 7. 实现函数柯里化(curry)
**考察点**:闭包、函数式编程
```js
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
}
return function (...args2) {
return curried.apply(this, args.concat(args2))
}
}
}
// 使用
const add = curry((a, b, c) => a + b + c)
add(1)(2)(3) // 6
add(1, 2)(3) // 6
add(1)(2, 3) // 6
// 无限柯里化(累加)
function infiniteCurry(fn) {
const args = []
return function inner(...newArgs) {
if (newArgs.length === 0) return fn(...args)
args.push(...newArgs)
return inner
}
}
const sum = infiniteCurry((...args) => args.reduce((a, b) => a + b, 0))
sum(1)(2)(3)() // 6
```
---
### 8. 实现 EventEmitter(发布订阅)
**考察点**:设计模式、事件系统
```js
class EventEmitter {
constructor() {
this.events = {}
}
on(event, listener) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(listener)
return this
}
once(event, listener) {
const wrapper = (...args) => {
listener(...args)
this.off(event, wrapper)
}
wrapper._original = listener
return this.on(event, wrapper)
}
emit(event, ...args) {
const listeners = this.events[event] || []
listeners.slice().forEach(fn => fn(...args))
return this
}
off(event, listener) {
if (!this.events[event]) return this
this.events[event] = this.events[event].filter(
fn => fn !== listener && fn._original !== listener
)
return this
}
}
// 使用
const emitter = new EventEmitter()
emitter.on('data', (d) => console.log('received:', d))
emitter.once('connect', () => console.log('connected!'))
emitter.emit('data', { id: 1 })
emitter.emit('connect') // 触发一次后自动移除
```
FILE:references/browser.md
# 浏览器原理面试题库
## 初级
### 题目1:简述浏览器渲染流程
**考察点:** 关键渲染路径、DOM/CSSOM 构建、渲染树
**参考答案:**
浏览器渲染流程(关键渲染路径):
1. **解析 HTML** → 构建 DOM 树
2. **解析 CSS** → 构建 CSSOM 树
3. **合并** DOM + CSSOM → 渲染树(Render Tree,只含可见节点)
4. **布局(Layout/Reflow)**:计算每个节点的几何信息(位置、尺寸)
5. **绘制(Paint)**:将节点绘制成像素
6. **合成(Composite)**:将多个图层合并输出到屏幕
```
HTML → DOM
↘
Render Tree → Layout → Paint → Composite
↗
CSS → CSSOM
```
优化建议:
- 将 CSS 放在 `<head>`,JS 放在 `</body>` 前(或加 `defer`/`async`)
- 避免 JS 阻塞解析(`defer` 异步加载,不阻塞,按顺序执行)
---
### 题目2:什么是重排(Reflow)和重绘(Repaint)?如何减少?
**考察点:** 触发条件、性能影响、优化手段
**参考答案:**
- **重排(Reflow)**:元素的几何属性变化,需重新计算布局,代价高
- **重绘(Repaint)**:外观变化但不影响布局(如颜色),代价低
- **重排一定触发重绘,重绘不一定重排**
触发重排的操作:
```javascript
// 读取布局属性会强制同步布局(强制重排)
const h = element.offsetHeight; // 读
element.style.height = h + 'px'; // 写 → 重排
// 优化:批量读,批量写
const h1 = el1.offsetHeight;
const h2 = el2.offsetHeight; // 先全部读
el1.style.height = h1 + 'px';
el2.style.height = h2 + 'px'; // 再全部写
```
减少重排的方法:
```javascript
// 1. 使用 DocumentFragment 批量操作 DOM
const frag = document.createDocumentFragment();
items.forEach(item => frag.appendChild(createEl(item)));
container.appendChild(frag);
// 2. 使用 CSS transform 代替 top/left(不触发重排)
el.style.transform = 'translateX(100px)'; // ✅
el.style.left = '100px'; // ❌
```
---
### 题目3:什么是事件循环(Event Loop)?宏任务和微任务的区别?
**考察点:** 调用栈、任务队列、执行顺序
**参考答案:**
JS 是单线程的,通过事件循环处理异步任务。
执行顺序:同步代码 → 微任务队列(全部清空)→ 宏任务(一个)→ 微任务 → 宏任务 ...
- **宏任务(MacroTask)**:`setTimeout`、`setInterval`、I/O、UI渲染
- **微任务(MicroTask)**:`Promise.then`、`queueMicrotask`、`MutationObserver`
```javascript
console.log('1'); // 同步
setTimeout(() => console.log('2')); // 宏任务
Promise.resolve().then(() => {
console.log('3'); // 微任务
Promise.resolve().then(() => console.log('4')); // 微任务
});
console.log('5'); // 同步
// 输出顺序:1 → 5 → 3 → 4 → 2
```
---
## 中级
### 题目4:V8 垃圾回收机制是什么?分代回收如何工作?
**考察点:** 新生代/老生代、Scavenge、Mark-Sweep、内存泄漏
**参考答案:**
V8 使用**分代垃圾回收**,将堆内存分为新生代和老生代:
**新生代(Young Generation)**:存放短命对象,空间小(约1-8MB)
- 使用 **Scavenge 算法**(复制算法):将存活对象复制到另一半空间,效率高
**老生代(Old Generation)**:存放长命对象,空间大
- 使用 **Mark-Sweep(标记清除)**:标记可达对象,清除未标记的
- 使用 **Mark-Compact(标记整理)**:整理碎片
**晋升条件**:新生代对象经历 2 次 Scavenge 后晋升到老生代
```javascript
// 常见内存泄漏场景
// 1. 意外全局变量
function leak() {
leakedVar = 'I am global'; // 忘写 let/const/var
}
// 2. 未清除的定时器
const timer = setInterval(() => { /* ... */ }, 1000);
// 记得 clearInterval(timer)
// 3. 闭包引用大对象
function createLeak() {
const bigData = new Array(1e6).fill('*');
return () => bigData[0]; // bigData 无法被回收
}
```
---
### 题目5:`requestAnimationFrame` 和 `setTimeout` 在动画中的区别?
**考察点:** 渲染时机、掉帧、性能
**参考答案:**
```javascript
// setTimeout:不精确,可能在一帧内多次触发或跳帧
let pos = 0;
function animate() {
pos += 2;
el.style.left = pos + 'px';
setTimeout(animate, 16); // ❌ 16ms 不精确
}
// requestAnimationFrame:与浏览器刷新率同步(通常60fps=16.7ms)
function animate(timestamp) {
pos += 2;
el.style.transform = `translateX(pospx)`;
requestAnimationFrame(animate); // ✅ 下一帧执行
}
requestAnimationFrame(animate);
// 取消动画
const rafId = requestAnimationFrame(animate);
cancelAnimationFrame(rafId);
```
`rAF` 优势:
- 浏览器隐藏标签页时自动暂停,节省资源
- 在每帧渲染前执行,避免丢帧
- 与显示器刷新率同步
---
### 题目6:Web Workers 是什么?如何与主线程通信?
**考察点:** 多线程、postMessage、使用场景
**参考答案:**
Web Workers 让 JS 在**独立线程**中运行,不阻塞主线程(UI线程)。
```javascript
// main.js - 主线程
const worker = new Worker('worker.js');
// 向 Worker 发消息
worker.postMessage({ data: largeArray, type: 'SORT' });
// 接收 Worker 消息
worker.onmessage = (e) => {
console.log('排序结果:', e.data);
};
// 错误处理
worker.onerror = (err) => console.error(err);
// 终止 Worker
worker.terminate();
```
```javascript
// worker.js - Worker 线程
self.onmessage = (e) => {
const { data, type } = e.data;
if (type === 'SORT') {
const sorted = data.sort((a, b) => a - b); // 耗时操作
self.postMessage(sorted); // 返回结果
}
};
```
**限制:** 无法访问 DOM、`window`、`document`;可使用 `fetch`、`XMLHttpRequest`、`IndexedDB`。
---
## 高级
### 题目7:浏览器的合成层(Compositing Layer)是什么?如何利用 GPU 加速?
**考察点:** 图层提升、will-change、避免过度使用
**参考答案:**
浏览器会将部分元素提升为独立的合成层,由 GPU 合成,跳过 Layout 和 Paint,性能极高。
触发合成层的条件:
- `transform: translateZ(0)` 或 `translate3d`
- `will-change: transform`(现代推荐方式)
- `opacity < 1`(配合 transition)
- `video`、`canvas`、`iframe`
```css
/* 触发 GPU 加速 */
.animated-element {
will-change: transform; /* 提前声明,浏览器预优化 */
}
/* 不推荐的 hack */
.old-hack {
transform: translateZ(0); /* 曾经的方法,现在用 will-change */
}
/* 动画完成后移除,避免内存占用 */
```
```javascript
el.addEventListener('animationend', () => {
el.style.willChange = 'auto'; // 释放合成层
});
```
**注意:** 合成层占用显存,过多会导致内存压力,不要滥用。
---
### 题目8:详细描述从输入 URL 到页面显示的完整过程
**考察点:** 网络请求、DNS、TCP、渲染、完整链路
**参考答案:**
1. **URL 解析**:解析协议、域名、路径
2. **DNS 查询**:本地缓存 → 系统缓存 → DNS 服务器 → 根域名服务器
3. **TCP 连接**:三次握手建立连接(HTTPS 还有 TLS 握手)
4. **发送 HTTP 请求**:请求头/体
5. **服务器响应**:返回 HTML
6. **浏览器解析渲染**:DOM → CSSOM → Render Tree → Layout → Paint → Composite
7. **加载子资源**:CSS、JS、图片(并行加载)
8. **JS 执行**:可能修改 DOM,触发重排重绘
9. **DOMContentLoaded**:DOM 解析完成
10. **load 事件**:所有资源加载完成
```javascript
// 性能监控关键时间点
performance.timing.domContentLoadedEventEnd
- performance.timing.navigationStart; // DCL 时间
window.addEventListener('load', () => {
const [entry] = performance.getEntriesByType('navigation');
console.log('Total load time:', entry.loadEventEnd);
});
```
---
### 题目9:浏览器缓存策略(强缓存 vs 协商缓存)详解
**考察点:** Cache-Control、ETag、Last-Modified、缓存决策流程
**参考答案:**
```
请求资源
↓
有缓存?
├── 否 → 发请求 → 缓存响应
└── 是 → 强缓存是否过期?
├── 未过期 → 直接用缓存(200 from cache)
└── 过期 → 发协商请求(带 ETag/Last-Modified)
├── 资源未变 → 304 Not Modified,用缓存
└── 资源已变 → 200,返回新资源
```
```http
# 强缓存:不请求服务器
Cache-Control: max-age=31536000, immutable # 1年
Expires: Wed, 21 Oct 2026 07:28:00 GMT # 旧方式
# 协商缓存:请求服务器验证
ETag: "abc123" # 内容哈希
Last-Modified: Tue, 22 Feb 2022 22:00:00 GMT
# 请求头(协商)
If-None-Match: "abc123"
If-Modified-Since: Tue, 22 Feb 2022 22:00:00 GMT
```
最佳实践:
- HTML:`no-cache`(每次协商)
- CSS/JS:`max-age=1年` + 文件名加 hash(如 `app.3f8a2b.js`)
- 图片:`max-age=1周` 或更长
FILE:references/css.md
# CSS 面试题库
## 初级
### 题目1:什么是 BFC?如何触发 BFC?
**考察点:** BFC 概念、触发条件、实际应用场景
**参考答案:**
BFC(Block Formatting Context,块级格式化上下文)是一个独立的渲染区域,内部元素的布局不影响外部。
触发 BFC 的常见方式:
- `overflow` 不为 `visible`(如 `hidden`、`auto`)
- `display: flex`、`display: grid`
- `position: absolute` 或 `position: fixed`
- `float` 不为 `none`
实际应用:清除浮动、防止 margin 塌陷
```css
/* 清除浮动 */
.container {
overflow: hidden; /* 触发 BFC */
}
/* 防止父子 margin 塌陷 */
.parent {
overflow: hidden;
}
```
---
### 题目2:CSS 选择器优先级如何计算?
**考察点:** 优先级权重、!important、继承
**参考答案:**
优先级从高到低:
1. `!important`(覆盖一切)
2. 行内样式(1000)
3. ID 选择器(0100)
4. 类/伪类/属性选择器(0010)
5. 标签/伪元素选择器(0001)
6. 通配符/继承(0000)
```css
/* 优先级示例 */
#app .title { color: red; } /* 0110 */
.title { color: blue; } /* 0010 */
/* 最终 #app .title 优先级更高,显示红色 */
/* !important 慎用 */
.title { color: green !important; } /* 最高优先级 */
```
---
### 题目3:Flexbox 中 `flex: 1` 是什么意思?
**考察点:** flex 缩写属性、flex-grow/shrink/basis
**参考答案:**
`flex: 1` 等价于 `flex: 1 1 0%`,即:
- `flex-grow: 1`:剩余空间等比分配
- `flex-shrink: 1`:空间不足时等比收缩
- `flex-basis: 0%`:初始尺寸为0,完全依赖 grow 分配
```css
.container {
display: flex;
}
.item {
flex: 1; /* 等分容器宽度 */
}
.item-double {
flex: 2; /* 占其他 flex:1 元素的2倍空间 */
}
```
---
## 中级
### 题目4:用 Grid 实现一个经典三栏布局(左右固定,中间自适应)
**考察点:** Grid 布局语法、fr 单位、grid-template-columns
**参考答案:**
```css
.layout {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: auto;
gap: 16px;
min-height: 100vh;
}
.left { background: #f0f0f0; }
.main { background: #fff; }
.right { background: #f0f0f0; }
```
```html
<div class="layout">
<aside class="left">左侧</aside>
<main class="main">主内容</main>
<aside class="right">右侧</aside>
</div>
```
`fr` 是弹性单位,`1fr` 会占用除固定宽度外的剩余空间。
---
### 题目5:CSS 动画 `transition` 和 `animation` 的区别?
**考察点:** 触发方式、关键帧、性能优化
**参考答案:**
| 对比项 | transition | animation |
|--------|-----------|-----------|
| 触发 | 需要状态变化触发 | 可自动播放 |
| 关键帧 | 只有起止两帧 | 可定义多个关键帧 |
| 循环 | 不支持 | 支持 iteration-count |
```css
/* transition:hover 触发 */
.btn {
background: blue;
transition: background 0.3s ease;
}
.btn:hover { background: red; }
/* animation:自动播放 */
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loader {
animation: spin 1s linear infinite;
}
```
性能建议:优先使用 `transform` 和 `opacity`,避免触发重排。
---
### 题目6:什么是响应式设计?常用媒体查询断点如何设置?
**考察点:** 媒体查询、移动优先、常见断点
**参考答案:**
响应式设计让页面在不同设备上自适应展示,核心是媒体查询(Media Queries)。
移动优先策略(推荐):
```css
/* 基础样式:移动端 */
.container { padding: 16px; }
/* 平板 ≥ 768px */
@media (min-width: 768px) {
.container { padding: 24px; }
}
/* 桌面 ≥ 1024px */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
margin: 0 auto;
padding: 32px;
}
}
/* 高分辨率屏幕 */
@media (-webkit-min-device-pixel-ratio: 2) {
.logo { background-image: url('[email protected]'); }
}
```
---
## 高级
### 题目7:CSS 变量(自定义属性)的工作原理及应用场景
**考察点:** 变量作用域、动态更新、主题切换
**参考答案:**
CSS 变量以 `--` 开头,通过 `var()` 引用,具有级联和继承特性。
```css
/* 定义全局变量 */
:root {
--color-primary: #3498db;
--spacing-md: 16px;
--border-radius: 8px;
}
/* 使用变量 */
.btn {
background: var(--color-primary);
padding: var(--spacing-md);
border-radius: var(--border-radius);
}
/* 暗色主题:覆盖变量 */
[data-theme="dark"] {
--color-primary: #2980b9;
}
/* JS 动态修改 */
```
```javascript
// 运行时修改主题色
document.documentElement.style.setProperty('--color-primary', '#e74c3c');
// 读取变量值
const color = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary');
```
优势:比 Sass 变量更灵活,可在运行时修改,支持 JS 交互。
---
### 题目8:解释 CSS 层叠上下文(Stacking Context)与 z-index 的关系
**考察点:** 层叠上下文创建条件、z-index 失效原因、绘制顺序
**参考答案:**
层叠上下文是一个独立的渲染层,内部元素的 `z-index` 只在该上下文内比较。
创建层叠上下文的条件:
- `position` 非 `static` 且 `z-index` 非 `auto`
- `opacity < 1`
- `transform`、`filter`、`will-change` 非 `none`
- `isolation: isolate`
```css
/* z-index 失效场景 */
.parent-a { position: relative; z-index: 1; }
.child-a { position: relative; z-index: 9999; } /* 被父级z-index限制 */
.parent-b { position: relative; z-index: 2; }
.child-b { position: relative; z-index: 1; }
/* child-b 始终在 child-a 上方,因为 parent-b > parent-a */
/* 用 isolation 创建隔离层 */
.modal-backdrop {
isolation: isolate;
}
```
---
### 题目9:如何实现 CSS 容器查询(Container Queries)?与媒体查询有何不同?
**考察点:** Container Queries 语法、与媒体查询的区别、组件化思维
**参考答案:**
容器查询让组件根据**父容器**尺寸而非视口宽度来响应,更适合组件化开发。
```css
/* 定义容器 */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 容器查询 */
@container card (min-width: 400px) {
.card {
display: flex;
flex-direction: row;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}
/* 对比媒体查询 */
@media (min-width: 768px) {
/* 依赖视口,多列布局中难以控制单个组件 */
.card { flex-direction: row; }
}
```
**核心区别:** 媒体查询基于视口,容器查询基于父容器,后者更适合可复用组件。
FILE:references/engineering.md
# 工程化面试题库
## 初级
### 题目1:Webpack 和 Vite 的主要区别是什么?
**考察点:** 构建原理、开发体验、打包机制
**参考答案:**
| 对比项 | Webpack | Vite |
|--------|---------|------|
| 开发模式 | 打包所有模块后启动 | 基于原生 ESM,按需编译 |
| 冷启动 | 慢(需打包) | 极快(无需打包) |
| HMR | 较慢(重新打包模块) | 极快(精确更新单模块) |
| 生产打包 | 自身打包 | 使用 Rollup |
| 生态 | 成熟,插件丰富 | 快速发展中 |
**Vite 快的原因:**
1. 开发时利用浏览器原生 ESM,无需打包,直接让浏览器请求模块
2. 使用 esbuild(Go编写)预编译依赖,比 JS 快 10-100 倍
3. HMR 精准到模块级别,更新极快
```javascript
// Vite 开发模式:浏览器直接请求 ESM
// <script type="module" src="/src/main.js">
// 浏览器遇到 import 自动请求对应文件
// Webpack 开发模式:将所有模块打包成 bundle
// <script src="/dist/bundle.js">
```
---
### 题目2:什么是 Tree Shaking?如何确保它生效?
**考察点:** 静态分析、ESM、副作用标记
**参考答案:**
Tree Shaking 是通过静态分析,移除未使用代码(dead code)的优化手段。
**生效条件:**
1. 必须使用 **ESM(ES Modules)**,不能用 CommonJS(`require`)
2. 代码必须无副作用,或在 `package.json` 中声明 `sideEffects`
```javascript
// ✅ 支持 Tree Shaking(ESM)
export function add(a, b) { return a + b; }
export function unused() { return 'never used'; }
// 只导入 add,unused 会被 shake 掉
import { add } from './math';
// ❌ 不支持 Tree Shaking(CommonJS)
module.exports = { add, unused };
const { add } = require('./math'); // 整个模块被引入
```
```json
// package.json:声明哪些文件有副作用
{
"sideEffects": ["*.css", "./src/polyfills.js"],
// 或声明无副作用
"sideEffects": false
}
```
---
### 题目3:什么是热模块替换(HMR)?如何实现的?
**考察点:** HMR 原理、WebSocket、模块热更新
**参考答案:**
HMR(Hot Module Replacement)在不刷新页面的情况下,实时替换、添加或删除模块。
**原理:**
1. Dev Server 监听文件变化
2. 文件变化后,通过 **WebSocket** 通知浏览器
3. 浏览器下载变更的模块
4. 运行时替换旧模块,保留应用状态
```javascript
// Vite/Webpack 中手动处理 HMR(Vue/React 插件已自动处理)
if (import.meta.hot) {
import.meta.hot.accept('./module.js', (newModule) => {
// 接收新模块,执行更新逻辑
newModule.render();
});
import.meta.hot.dispose(() => {
// 模块被替换前的清理工作
clearInterval(timer);
});
}
```
React Fast Refresh 和 Vue HMR 都是基于此机制,框架层面保证组件状态不丢失。
---
## 中级
### 题目4:Monorepo 是什么?主流方案(pnpm workspace / Turborepo)如何选择?
**考察点:** Monorepo 优缺点、工具选型、依赖管理
**参考答案:**
**Monorepo**:将多个项目/包放在同一个 Git 仓库中管理。
优点:代码复用、统一工具链、原子提交、易于重构
缺点:仓库体积大、需要专门工具、CI 需按需构建
```
# pnpm workspace 结构
my-monorepo/
├── packages/
│ ├── ui/ # 组件库
│ ├── utils/ # 工具函数
│ └── app/ # 主应用
├── pnpm-workspace.yaml
└── package.json
```
```yaml
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
```
```json
// packages/app/package.json:引用内部包
{
"dependencies": {
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
```
**工具选型:**
- **pnpm workspace**:基础 Monorepo,依赖管理
- **Turborepo**:在 workspace 基础上加任务编排、增量构建缓存
- **Nx**:功能最全,适合大型企业项目
---
### 题目5:Webpack 的核心概念:Loader 和 Plugin 的区别?
**考察点:** 转换 vs 扩展、工作时机、使用场景
**参考答案:**
| 对比 | Loader | Plugin |
|------|--------|--------|
| 作用 | 转换文件(转换器) | 扩展构建能力(扩展器) |
| 时机 | 模块加载阶段 | 整个构建生命周期 |
| 形式 | 函数,返回转换结果 | 类,监听 webpack 事件 |
```javascript
// webpack.config.js
module.exports = {
module: {
rules: [
// Loader:处理 .scss 文件
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'] // 从右到左执行
},
{
test: /\.tsx?$/,
use: 'ts-loader'
}
]
},
plugins: [
// Plugin:生成 HTML 文件
new HtmlWebpackPlugin({ template: './index.html' }),
// Plugin:抽取 CSS 到单独文件
new MiniCssExtractPlugin({ filename: '[name].[hash].css' }),
// Plugin:打包前清理 dist
new CleanWebpackPlugin()
]
};
```
---
### 题目6:如何优化 Webpack 的构建速度和产物体积?
**考察点:** 缓存、并行、代码分割、分析工具
**参考答案:**
**构建速度优化:**
```javascript
module.exports = {
// 1. 开启持久化缓存(Webpack 5)
cache: { type: 'filesystem' },
// 2. 缩小解析范围
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
extensions: ['.js', '.ts'] // 减少后缀尝试
},
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/, // 排除 node_modules
use: ['thread-loader', 'babel-loader'] // thread-loader 开启多线程
}]
}
};
```
**产物体积优化:**
```javascript
// 代码分割(Code Splitting)
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
// 动态导入(懒加载)
const HomePage = lazy(() => import('./pages/Home'));
```
---
## 高级
### 题目7:设计一套前端 CI/CD 流程,需要考虑哪些环节?
**考察点:** 流水线设计、质量门禁、部署策略
**参考答案:**
```yaml
# .github/workflows/ci.yml 示例
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
quality:
steps:
- uses: actions/checkout@v3
- name: Install deps
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
- name: Type check
run: pnpm tsc --noEmit
- name: Unit tests
run: pnpm test --coverage
- name: Build
run: pnpm build
deploy-staging:
needs: quality
if: github.ref == 'refs/heads/develop'
steps:
- name: Deploy to Staging
run: pnpm deploy:staging
deploy-prod:
needs: quality
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to Production
run: pnpm deploy:prod
```
**部署策略选择:**
- **蓝绿部署**:两套环境切换,零停机,回滚快
- **金丝雀发布**:逐步放量(1% → 10% → 100%),降低风险
- **滚动更新**:逐步替换实例,Kubernetes 默认策略
---
### 题目8:Vite 插件开发:如何实现一个自定义 Vite 插件?
**考察点:** Vite 插件 API、钩子函数、Rollup 兼容
**参考答案:**
Vite 插件基于 Rollup 插件接口扩展,额外提供 Vite 专有钩子。
```javascript
// vite-plugin-auto-import-css.js
export default function autoImportCssPlugin() {
return {
name: 'auto-import-css', // 插件名称(必须)
// 构建开始时
buildStart() {
console.log('Build started');
},
// 转换模块内容
transform(code, id) {
if (!id.endsWith('.vue')) return null;
// 自动注入 CSS import
const cssPath = id.replace('.vue', '.css');
return {
code: `import 'cssPath';\ncode`,
map: null
};
},
// 解析模块 ID
resolveId(source) {
if (source === 'virtual:my-module') {
return '\0virtual:my-module'; // \0 前缀标记虚拟模块
}
},
// 加载虚拟模块
load(id) {
if (id === '\0virtual:my-module') {
return 'export const msg = "from virtual module"';
}
},
// Vite 专有:开发服务器配置
configureServer(server) {
server.middlewares.use('/api/ping', (req, res) => {
res.end('pong');
});
}
};
}
```
---
### 题目9:Module Federation(模块联邦)原理及应用场景
**考察点:** 微前端、运行时共享、远程模块加载
**参考答案:**
Module Federation(Webpack 5)允许多个独立构建的应用在**运行时**共享代码,是微前端的重要实现方式。
```javascript
// 被消费方(remote):暴露模块
// webpack.config.js (app-remote)
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/components/Button',
'./utils': './src/utils/index'
},
shared: ['react', 'react-dom'] // 共享依赖,避免重复加载
});
// 消费方(host):使用远程模块
// webpack.config.js (app-host)
new ModuleFederationPlugin({
name: 'hostApp',
remotes: {
remoteApp: 'remoteApp@http://remote.example.com/remoteEntry.js'
},
shared: ['react', 'react-dom']
});
// 在代码中使用远程组件
const Button = React.lazy(() => import('remoteApp/Button'));
```
**适用场景:**
- 大型应用拆分为多个独立部署的子应用
- 多团队协作,各自维护独立仓库
- 跨项目复用组件,无需发布 npm 包
FILE:references/javascript.md
# JavaScript 核心面试题库
## 目录
- [初级题目](#初级)
- [中级题目](#中级)
- [高级题目](#高级)
- [专家题目](#专家)
- [代码手写题](#代码手写题)
---
## 初级
### JS-J-01 什么是闭包?
**难度:** 初级
**考察点:** 作用域、词法环境
**标准答案要点:**
- 闭包是函数与其词法作用域(Lexical Environment)的组合
- 内部函数可以访问外部函数的变量,即使外部函数已经执行完毕
- 经典应用:数据私有化、函数工厂、防抖节流
**代码示例:**
```javascript
function counter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const c = counter();
c.increment(); // 1
c.increment(); // 2
```
**追问:**
1. 闭包可能导致什么问题?如何避免?(内存泄漏:变量无法被 GC,循环中使用 let 替代 var)
2. `for` 循环中 `var` 和 `let` 的闭包行为差异?
---
### JS-J-02 `==` 和 `===` 的区别
**难度:** 初级
**考察点:** 类型转换、隐式强制转换
**标准答案要点:**
- `===` 严格相等:不进行类型转换,类型和值都必须相同
- `==` 宽松相等:会进行类型强制转换(ToNumber、ToPrimitive)
- 典型陷阱:`null == undefined` 为 `true`,`null === undefined` 为 `false`
**追问:** `[] == false` 为什么是 `true`?(ToPrimitive → "" → 0,Boolean → 0,0 == 0)
---
### JS-J-03 `var`、`let`、`const` 的区别
**难度:** 初级
**考察点:** 变量声明、作用域、提升
| 特性 | var | let | const |
|------|-----|-----|-------|
| 作用域 | 函数作用域 | 块作用域 | 块作用域 |
| 变量提升 | 提升(undefined) | 存在但 TDZ | 存在但 TDZ |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 重新赋值 | 允许 | 允许 | 不允许 |
| 挂载到 window | 是 | 否 | 否 |
**TDZ(Temporal Dead Zone):** let/const 声明前访问会抛出 `ReferenceError`
---
### JS-J-04 什么是事件冒泡和事件捕获?
**难度:** 初级
**考察点:** 事件模型、DOM
**标准答案要点:**
- **捕获阶段**:事件从 window → document → ... → 目标元素
- **目标阶段**:事件到达目标元素
- **冒泡阶段**:事件从目标元素 → ... → window
- `addEventListener` 第三个参数 `true` 为捕获,`false`(默认)为冒泡
- `stopPropagation()` 阻止传播;`stopImmediatePropagation()` 还阻止同元素其他监听器
- `preventDefault()` 阻止默认行为
**追问:** 事件委托的原理和优势?(利用冒泡,减少事件绑定数量,动态元素也适用)
---
## 中级
### JS-M-01 详解 JavaScript 事件循环(Event Loop)
**难度:** 中级
**考察点:** 异步机制、宏任务/微任务
**标准答案要点:**
执行顺序:
1. 同步代码(Call Stack)
2. 清空微任务队列(Microtask Queue):`Promise.then`、`MutationObserver`、`queueMicrotask`
3. 取一个宏任务(Macrotask/Task Queue):`setTimeout`、`setInterval`、`I/O`、`MessageChannel`
4. 重复 2-3
```javascript
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// 输出:1 4 3 2
```
**追问:**
1. `queueMicrotask` vs `Promise.resolve().then` 的区别?(功能相同,queueMicrotask 更语义化)
2. Node.js 中的 `process.nextTick` 和 Promise 的优先级?(nextTick 优先)
3. `requestAnimationFrame` 属于宏任务还是微任务?(宏任务,在下一帧渲染前执行)
---
### JS-M-02 Promise 深度解析
**难度:** 中级
**考察点:** 异步编程、错误处理
**核心知识点:**
- Promise 的三种状态:Pending → Fulfilled / Rejected(不可逆)
- `.then()` 返回新的 Promise,支持链式调用
- `.catch()` 等价于 `.then(null, rejection)`
- Promise 错误不被捕获不会崩溃(静默失败),但现代浏览器会在 console 报 `UnhandledPromiseRejection`
**Promise.xxx 对比:**
```javascript
// Promise.all - 全部成功才成功,一个失败立即失败
Promise.all([p1, p2, p3])
// Promise.allSettled - 等所有完成,返回每个结果(含状态)
Promise.allSettled([p1, p2, p3])
// Promise.race - 第一个完成(无论成败)
Promise.race([p1, p2, p3])
// Promise.any - 第一个成功;全部失败才失败(AggregateError)
Promise.any([p1, p2, p3])
```
**手写 Promise(面试高频):**
```javascript
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.callbacks = [];
const resolve = (value) => {
if (this.state !== 'pending') return;
this.state = 'fulfilled';
this.value = value;
this.callbacks.forEach(cb => cb.onFulfilled(value));
};
const reject = (reason) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.value = reason;
this.callbacks.forEach(cb => cb.onRejected(reason));
};
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handle = (fn, value) => {
try {
const result = fn ? fn(value) : value;
result instanceof MyPromise ? result.then(resolve, reject) : resolve(result);
} catch (e) {
reject(e);
}
};
if (this.state === 'fulfilled') handle(onFulfilled, this.value);
else if (this.state === 'rejected') handle(onRejected, this.value);
else this.callbacks.push({
onFulfilled: v => handle(onFulfilled, v),
onRejected: r => handle(onRejected, r)
});
});
}
}
```
---
### JS-M-03 原型链与继承
**难度:** 中级
**考察点:** OOP、原型机制
**核心要点:**
- 每个对象有 `[[Prototype]]`(通过 `__proto__` 访问,或 `Object.getPrototypeOf()`)
- 函数有 `prototype` 属性,`new` 操作时赋值给实例的 `[[Prototype]]`
- 原型链终止于 `Object.prototype.__proto__ === null`
```javascript
function Animal(name) { this.name = name; }
Animal.prototype.speak = function() { return `this.name speaks`; };
function Dog(name) { Animal.call(this, name); }
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() { return 'Woof!'; };
const d = new Dog('Rex');
d instanceof Dog; // true
d instanceof Animal; // true
```
**`new` 操作符的步骤:**
1. 创建空对象
2. 设置 `__proto__` = 构造函数的 `prototype`
3. 执行构造函数(`this` 指向新对象)
4. 若构造函数返回对象,则返回该对象;否则返回新对象
**追问:** `class` 语法糖与原型继承的本质关系?(class 本质是基于原型的语法糖,`extends` 会设置原型链)
---
### JS-M-04 `this` 指向详解
**难度:** 中级
**考察点:** 执行上下文
| 场景 | this 指向 |
|------|----------|
| 全局函数调用 | `window`(严格模式为 `undefined`) |
| 方法调用 | 调用该方法的对象 |
| `new` 调用 | 新创建的对象 |
| `call/apply/bind` | 第一个参数指定的对象 |
| 箭头函数 | 词法作用域(外层 this) |
| 事件处理器 | 绑定元素(除非箭头函数) |
**常见陷阱:**
```javascript
const obj = {
name: 'test',
greet: function() {
setTimeout(function() {
console.log(this.name); // undefined (this = window)
}, 0);
setTimeout(() => {
console.log(this.name); // 'test' (箭头函数捕获外层 this)
}, 0);
}
};
```
---
## 高级
### JS-S-01 JavaScript 内存管理与垃圾回收
**难度:** 高级
**考察点:** 运行时、性能
**V8 内存结构:**
- **新生代(Young Generation)**:存活时间短的对象,使用 Scavenge 算法(Semi-Space),From/To 区交替
- **老生代(Old Generation)**:存活时间长的对象,使用标记-清除(Mark-Sweep)+ 标记-整理(Mark-Compact)
- **大对象空间**:单独管理,不进入正常 GC 流程
**常见内存泄漏场景:**
1. 意外的全局变量(未声明变量)
2. 未清理的定时器 / 事件监听器
3. 闭包持有大量数据
4. 已移除 DOM 节点的引用仍在 JS 中
**WeakMap/WeakSet 的价值:** 弱引用不阻止 GC,适合缓存 DOM 节点引用
---
### JS-S-02 ES6+ 高级特性深度解析
**难度:** 高级
**Generator 函数:**
```javascript
function* idGenerator() {
let id = 1;
while (true) yield id++;
}
// 协程实现异步(co 库原理)
function* fetchUser(id) {
const user = yield fetch(`/user/id`);
const posts = yield fetch(`/posts?userId=user.id`);
return posts;
}
```
**Proxy 与 Reflect:**
```javascript
const handler = {
get(target, prop, receiver) {
console.log(`Getting prop`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Setting prop = value`);
return Reflect.set(target, prop, value, receiver);
}
};
const p = new Proxy({}, handler);
// Vue 3 响应式系统的核心基础
```
---
## 专家
### JS-E-01 JavaScript 引擎优化
**难度:** 专家
**考察点:** JIT 编译、性能优化
**V8 优化关键:**
- **隐藏类(Hidden Classes)**:V8 为相同属性顺序的对象创建相同 Hidden Class,实现属性访问优化
- **内联缓存(Inline Cache)**:缓存属性访问的位置信息,避免重复查找
- **JIT 编译**:热点代码由 Ignition(解释器)→ Turbofan(优化编译器)
**反优化(Deoptimization)触发条件:**
- 对象属性动态增减(破坏 Hidden Class)
- 函数参数类型变化(影响 IC)
- `arguments` 对象使用
- `try-catch` 内的代码(部分情况)
**追问:** 如何通过 Chrome DevTools 检测 V8 反优化?(`--trace-deopt` 或 Performance 面板)
---
## 代码手写题
### 防抖(Debounce)
```javascript
function debounce(fn, delay, immediate = false) {
let timer = null;
return function(...args) {
const callNow = immediate && !timer;
clearTimeout(timer);
timer = setTimeout(() => {
timer = null;
if (!immediate) fn.apply(this, args);
}, delay);
if (callNow) fn.apply(this, args);
};
}
```
### 节流(Throttle)
```javascript
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
```
### 深拷贝(Deep Clone)
```javascript
function deepClone(obj, map = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (map.has(obj)) return map.get(obj); // 处理循环引用
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key], map);
}
return clone;
}
```
### 柯里化(Curry)
```javascript
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
```
### 发布订阅(EventEmitter)
```javascript
class EventEmitter {
constructor() { this.events = {}; }
on(event, fn) {
(this.events[event] ??= []).push(fn);
return this;
}
once(event, fn) {
const wrapper = (...args) => { fn(...args); this.off(event, wrapper); };
return this.on(event, wrapper);
}
emit(event, ...args) {
this.events[event]?.forEach(fn => fn(...args));
return this;
}
off(event, fn) {
this.events[event] = this.events[event]?.filter(f => f !== fn);
return this;
}
}
```
FILE:references/network.md
# 网络与安全面试题
## 初级
### 1. HTTP 和 HTTPS 的区别?
**考察点**:协议基础、安全意识
**参考答案**:
- HTTP 明文传输,HTTPS = HTTP + TLS/SSL 加密
- HTTPS 需要 CA 证书,默认端口 443(HTTP 是 80)
- HTTPS 防止中间人攻击、数据篡改、窃听
**TLS 握手简述**:
1. Client Hello(支持的加密套件、随机数)
2. Server Hello + 证书
3. 客户端验证证书,生成 Pre-Master Secret,用服务器公钥加密发送
4. 双方用三个随机数生成 Session Key,后续对称加密通信
---
### 2. HTTP 状态码有哪些,各自含义?
**考察点**:HTTP 基础
**参考答案**:
- **2xx 成功**:200 OK、201 Created、204 No Content
- **3xx 重定向**:301 永久重定向、302 临时重定向、304 Not Modified
- **4xx 客户端错误**:400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found
- **5xx 服务端错误**:500 Internal Server Error、502 Bad Gateway、503 Service Unavailable
---
### 3. 什么是 Cookie 和 Session?区别是什么?
**考察点**:状态管理基础
**参考答案**:
- **Cookie**:存储在浏览器,随请求自动携带,可设置过期时间
- **Session**:存储在服务端,通过 Session ID(通常存在 Cookie 中)标识用户
| 对比 | Cookie | Session |
|------|--------|---------|
| 存储位置 | 浏览器 | 服务器 |
| 安全性 | 较低(可被篡改) | 较高 |
| 容量 | ~4KB | 无限制 |
| 性能 | 不占服务器资源 | 占服务器内存 |
---
## 中级
### 4. HTTP/1.1、HTTP/2、HTTP/3 的区别?
**考察点**:协议演进、性能优化
**参考答案**:
**HTTP/1.1**:
- 持久连接(Keep-Alive),但同一连接串行处理请求
- 队头阻塞问题(Head-of-Line Blocking)
- 通过多域名分片、雪碧图等方式优化
**HTTP/2**:
- 二进制分帧(Binary Framing)
- 多路复用(Multiplexing):一个连接并行多个请求流
- 头部压缩(HPACK)
- 服务器推送(Server Push)
- 仍存在 TCP 层队头阻塞
**HTTP/3**:
- 基于 QUIC 协议(UDP),彻底解决队头阻塞
- 0-RTT 或 1-RTT 建立连接
- 连接迁移(切换网络不断连)
---
### 5. 什么是 CORS?如何解决跨域问题?
**考察点**:浏览器安全、跨域方案
**参考答案**:
CORS(Cross-Origin Resource Sharing):浏览器同源策略限制跨域请求,服务端通过响应头允许跨域。
**简单请求**(GET/POST + 普通 headers):
```http
# 服务端响应头
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true
```
**预检请求**(OPTIONS):非简单请求先发 OPTIONS:
```http
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
```
**其他跨域方案**:
- 开发环境:Vite/Webpack devServer proxy
- JSONP:仅 GET,已过时
- Nginx 反向代理:生产常用
- PostMessage:iframe 通信
---
### 6. XSS 和 CSRF 攻击原理及防御?
**考察点**:前端安全
**参考答案**:
**XSS(跨站脚本攻击)**:
- 原理:注入恶意脚本到页面,窃取 Cookie/劫持会话
- 类型:存储型(持久)、反射型(URL参数)、DOM型
- 防御:
```js
// 1. 输出编码
const escape = (str) => str.replace(/[&<>"']/g, (c) => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
})[c])
// 2. CSP 响应头
Content-Security-Policy: default-src 'self'; script-src 'self'
// 3. HttpOnly Cookie(JS无法读取)
Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict
```
**CSRF(跨站请求伪造)**:
- 原理:利用用户已登录状态,诱导发起恶意请求
- 防御:
```js
// 1. CSRF Token(表单/请求头携带)
headers: { 'X-CSRF-Token': getCsrfToken() }
// 2. SameSite Cookie
Set-Cookie: session=xxx; SameSite=Strict
// 3. 检查 Referer/Origin
```
---
## 高级
### 7. JWT 和 Cookie-Session 方案如何选择?
**考察点**:认证方案设计
**参考答案**:
**JWT(JSON Web Token)**:
```
Header.Payload.Signature
// Payload 包含用户信息,服务端无需存储状态
```
| 对比 | JWT | Cookie-Session |
|------|-----|----------------|
| 服务端存储 | 无状态 | 需存储 Session |
| 横向扩展 | 天然支持 | 需共享 Session(Redis) |
| 注销 | 困难(需黑名单) | 直接删除 Session |
| 安全性 | Payload 可解码(非加密) | 服务端控制 |
| 适用场景 | 微服务、移动端、跨域 | 传统 Web 应用 |
**最佳实践**:
- Access Token 短期(15min) + Refresh Token 长期(7天)
- Refresh Token 存 HttpOnly Cookie,Access Token 存内存
- 注销时将 Refresh Token 加黑名单
---
### 8. 浏览器缓存策略详解?
**考察点**:缓存机制、性能优化
**参考答案**:
**强缓存**(不发请求):
```http
Cache-Control: max-age=31536000, immutable # 推荐
Expires: Wed, 01 Jan 2026 00:00:00 GMT # 旧方式
```
**协商缓存**(发请求验证):
```http
# 服务端返回
Last-Modified: Tue, 01 Apr 2026 12:00:00 GMT
ETag: "abc123"
# 客户端请求携带
If-Modified-Since: Tue, 01 Apr 2026 12:00:00 GMT
If-None-Match: "abc123"
# 未变化 → 304 Not Modified
```
**最佳实践**:
- HTML:`Cache-Control: no-cache`(每次验证)
- JS/CSS(带 hash):`Cache-Control: max-age=31536000, immutable`
- API:`Cache-Control: no-store`
**缓存优先级**:Service Worker > Memory Cache > Disk Cache > 网络请求
FILE:references/react.md
# React 深度面试题库
## 目录
- [初级](#初级)
- [中级](#中级)
- [高级](#高级)
- [专家](#专家)
---
## 初级
### R-J-01 React 组件生命周期(Class + Hooks 对比)
**难度:** 初级
| 生命周期 | Class | Hooks |
|---------|-------|-------|
| 挂载 | `componentDidMount` | `useEffect(() => {...}, [])` |
| 更新 | `componentDidUpdate` | `useEffect(() => {...}, [deps])` |
| 卸载 | `componentWillUnmount` | `useEffect(() => { return () => {...} }, [])` |
| 错误捕获 | `componentDidCatch` | 无(需 ErrorBoundary class 组件) |
| 渲染优化 | `shouldComponentUpdate` / `PureComponent` | `React.memo` / `useMemo` |
---
### R-J-02 受控组件 vs 非受控组件
**难度:** 初级
- **受控组件**:表单数据由 React state 控制,每次输入触发 setState
- **非受控组件**:数据由 DOM 直接管理,通过 `ref` 获取
- **选择原则**:需要即时校验/联动用受控;简单表单/文件上传用非受控
---
## 中级
### R-M-01 React Fiber 架构
**难度:** 中级/高级
**考察点:** 渲染机制、并发
**为什么需要 Fiber(React 16 重写):**
- 旧版 Stack Reconciler:同步递归,无法中断,大量节点更新时阻塞主线程
- Fiber:将渲染工作分解为小单元(Fiber Node),可中断、恢复、优先级调度
**Fiber 节点关键属性:**
```
FiberNode {
type, // 组件类型(函数/类/DOM标签)
key, // Diff 用的 key
child, // 第一个子节点
sibling, // 下一个兄弟节点
return, // 父节点
alternate, // 双缓冲:当前树 ↔ WorkInProgress 树
effectTag, // 副作用标记(Placement/Update/Deletion)
memoizedState, // Hooks 链表(函数组件)
pendingProps,
memoizedProps,
}
```
**两个阶段:**
1. **Render/Reconcile 阶段**(可中断):构建 WorkInProgress Fiber 树,计算副作用
2. **Commit 阶段**(不可中断):
- `beforeMutation`:执行 `getSnapshotBeforeUpdate`,异步调度 `useEffect`
- `mutation`:操作真实 DOM
- `layout`:执行 `componentDidMount/Update`,同步调度 `useLayoutEffect`
**优先级(Lanes 模型,React 18):**
```
SyncLane (1) → 同步(点击等离散事件)
InputContinuousLane → 连续输入(拖动、滚动)
DefaultLane → 默认更新
TransitionLane → startTransition 标记的过渡更新
IdleLane → 空闲更新
```
---
### R-M-02 React Diff 算法
**难度:** 中级
**考察点:** 虚拟 DOM、性能
**三个假设(启发式策略):**
1. 不同类型的节点产生不同子树(直接替换)
2. 通过 `key` 标识跨层级移动的节点
3. 同层比较,不跨层级比较
**单节点 Diff 流程:**
```
新节点 vs 旧节点
├── type && key 都相同 → 复用,标记 Update
├── key 相同但 type 不同 → 删除旧节点,创建新节点
└── key 不同 → 删除旧节点,继续遍历
```
**多节点 Diff(两轮遍历):**
- 第一轮:处理节点更新(type 相同、key 相同)
- 第二轮:处理新增、删除、移动(使用 Map 优化查找)
**为什么 key 不能用 index:**
- 列表逆序/插入时,index 变化导致大量不必要的重渲染
- 带状态的组件(如 input)会复用错误的 Fiber,状态错位
---
### R-M-03 Hooks 深度解析
**难度:** 中级/高级
**useState 实现原理(简化):**
```javascript
// Hooks 以链表形式存储在 Fiber.memoizedState 上
let workInProgressFiber;
let workInProgressHook;
function useState(initialState) {
let hook;
if (isMount) {
hook = { memoizedState: initialState, queue: { dispatch: null, pending: null }, next: null };
// 追加到链表末尾
} else {
hook = workInProgressHook; // 按顺序取
workInProgressHook = workInProgressHook.next;
}
const dispatch = (action) => {
hook.queue.pending = { action, next: null };
scheduleUpdateOnFiber(workInProgressFiber);
};
return [hook.memoizedState, dispatch];
}
```
**为什么 Hooks 不能在条件语句中调用:**
- Hooks 通过链表顺序维护对应关系
- 条件调用导致顺序错乱,Hook 取到错误状态
**useEffect vs useLayoutEffect:**
| | useEffect | useLayoutEffect |
|--|-----------|-----------------|
| 执行时机 | 浏览器绘制后(异步) | DOM 更新后、绘制前(同步) |
| 适用场景 | 数据请求、订阅 | 需要读取/修改 DOM 布局 |
| SSR | 可用 | 会产生警告 |
**useMemo vs useCallback:**
```javascript
// useMemo 缓存计算结果
const expensiveValue = useMemo(() => compute(a, b), [a, b]);
// useCallback 缓存函数引用
const memoizedFn = useCallback(() => doSomething(a), [a]);
// 等价于 useMemo(() => () => doSomething(a), [a])
```
---
## 高级
### R-S-01 React 18 并发特性
**难度:** 高级
**startTransition:**
```javascript
// 将低优先级更新标记为 Transition
import { startTransition } from 'react';
function handleInput(e) {
// 高优先级:立即更新输入框
setInputValue(e.target.value);
// 低优先级:可中断的过渡更新
startTransition(() => {
setSearchResults(filter(e.target.value));
});
}
```
**useDeferredValue:**
```javascript
// 延迟渲染低优先级内容
const deferredQuery = useDeferredValue(query);
return <SearchResults query={deferredQuery} />;
```
**Suspense 与数据获取:**
```javascript
// React 18 Suspense + 异步组件
function UserProfile({ userId }) {
const user = use(fetchUser(userId)); // React 19 的 use hook
return <div>{user.name}</div>;
}
<Suspense fallback={<Skeleton />}>
<UserProfile userId={1} />
</Suspense>
```
**React 18 自动批处理(Automatic Batching):**
- React 17:仅在 React 事件处理器中批处理
- React 18:所有异步更新(setTimeout、fetch 回调)都自动批处理
- 退出批处理:`flushSync(() => setState(...))`
---
### R-S-02 状态管理方案对比
**难度:** 高级
| 方案 | 原理 | 优势 | 局限 |
|------|------|------|------|
| Context + useReducer | 内置,基于发布订阅 | 无需依赖 | 任意 context 变化触发所有消费者重渲 |
| Zustand | 基于 `useSyncExternalStore` | 简洁,selector 精准更新 | 生态相对小 |
| Redux Toolkit | 单一数据源,纯函数 reducer | 可预测,DevTools 强大 | 样板代码多 |
| Jotai | 原子化状态(atom) | 精细控制,无 Provider | 原子数量多时难管理 |
| Recoil | 原子 + 选择器 | 异步支持好 | Meta 已停更 |
| Valtio | 基于 Proxy | 直接修改,简洁 | 不支持 IE |
---
## 专家
### R-E-01 React Server Components (RSC)
**难度:** 专家
**核心概念:**
- **RSC(Server Components)**:在服务端执行,无 state/effects,可直接访问数据库/文件系统
- **RCC(Client Components)**:添加 `'use client'` 指令,包含交互逻辑
- **RSC Payload**:服务端序列化组件树(JSON-like 格式),传输给客户端
**边界规则:**
```
Server Component
└── Client Component ('use client')
└── ❌ 不能直接导入 Server Component
└── ✅ 可以通过 children prop 接收 Server Component
```
**优势:**
- 零 JS 体积(服务端组件不发送 JS 到客户端)
- 直接数据库查询,无需 API
- 自动代码分割
**追问:** RSC 与 SSR 的区别?
- SSR:在服务端渲染为 HTML,客户端 hydration
- RSC:组件在服务端运行,通过 RSC 协议传输,不一定生成 HTML
---
### R-E-02 性能优化实践
**难度:** 高级/专家
**渲染优化层级:**
```
1. React.memo → 组件级记忆化(props 浅比较)
2. useMemo → 计算结果记忆化
3. useCallback → 函数引用稳定化
4. Context 拆分 → 避免不相关状态更新触发重渲
5. 虚拟列表 → react-virtual / @tanstack/virtual
6. Code Splitting → React.lazy + Suspense
7. 并发特性 → startTransition / useDeferredValue
```
**Profile 工具:**
- React DevTools Profiler:火焰图分析渲染耗时
- `<React.StrictMode>`:开发模式双调用检测副作用
- `why-did-you-render`:追踪不必要的重渲染
FILE:references/typescript.md
# TypeScript 进阶面试题库
## 目录
- [初级](#初级)
- [中级](#中级)
- [高级](#高级)
- [专家](#专家)
---
## 初级
### TS-J-01 TypeScript 的核心优势
**难度:** 初级
**要点:**
- 静态类型检查,编译期捕获错误
- 更好的 IDE 支持(自动补全、重构)
- 类型即文档,提升代码可读性
- 渐进式采用,兼容 JavaScript
**追问:** TypeScript 的类型是强类型还是弱类型?(结构化类型系统 Structural Typing,duck typing)
---
### TS-J-02 interface vs type 的区别
**难度:** 初级
| 特性 | interface | type |
|------|-----------|------|
| 声明合并 | ✅ 支持 | ❌ 不支持 |
| 扩展语法 | `extends` | `&` 交叉类型 |
| 基础类型别名 | ❌ | ✅ `type ID = string` |
| 联合类型 | ❌ | ✅ `type A = B \| C` |
| 计算属性 | 有限支持 | ✅ 完全支持 |
| 映射类型 | ❌ | ✅ |
**最佳实践:** 公共 API 用 interface(支持声明合并),内部实现用 type(更灵活)
---
## 中级
### TS-M-01 泛型(Generics)深度解析
**难度:** 中级
**泛型约束:**
```typescript
// 约束泛型必须有 length 属性
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
// 泛型类
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
// 泛型工厂函数
function create<T>(ctor: new () => T): T {
return new ctor();
}
```
**条件类型(Conditional Types):**
```typescript
type IsArray<T> = T extends any[] ? true : false;
type IsString = IsArray<string>; // false
type IsNumbers = IsArray<number[]>; // true
// infer 关键字提取类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
```
---
### TS-M-02 内置工具类型(Utility Types)
**难度:** 中级
```typescript
// Partial - 所有属性变为可选
type Partial<T> = { [P in keyof T]?: T[P]; }
// Required - 所有属性变为必须
type Required<T> = { [P in keyof T]-?: T[P]; }
// Readonly - 所有属性变为只读
type Readonly<T> = { readonly [P in keyof T]: T[P]; }
// Pick - 选取部分属性
type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
// Omit - 排除部分属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Record - 构建对象类型
type Record<K extends string, V> = { [P in K]: V; }
// Exclude - 从联合类型中排除
type Exclude<T, U> = T extends U ? never : T;
// Extract - 从联合类型中提取
type Extract<T, U> = T extends U ? T : never;
// NonNullable - 排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
```
**面试常见手写:**
```typescript
// 手写 DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 手写 DeepPartial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
```
---
### TS-M-03 映射类型与模板字面量类型
**难度:** 中级/高级
**映射类型:**
```typescript
// 键值转换
type MappedType<T> = {
[K in keyof T as `getCapitalize<string & K>`]: () => T[K];
};
interface User { name: string; age: number; }
type UserGetters = MappedType<User>;
// { getName: () => string; getAge: () => number; }
```
**模板字面量类型:**
```typescript
type EventName<T extends string> = `onCapitalize<T>`;
type ClickEvent = EventName<'click'>; // 'onClick'
// 组合使用
type CSSProperty = `string-string`;
type BEM<B extends string, E extends string, M extends string> =
`B__E--M`;
```
---
## 高级
### TS-S-01 装饰器(Decorators)
**难度:** 高级
**考察点:** 元编程、AOP
**装饰器类型:**
```typescript
// 类装饰器
function Sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
// 方法装饰器
function Log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling key with`, args);
const result = original.apply(this, args);
console.log(`key returned`, result);
return result;
};
return descriptor;
}
// 属性装饰器
function Validate(target: any, key: string) {
let value: string;
Object.defineProperty(target, key, {
get: () => value,
set: (newVal: string) => {
if (!newVal) throw new Error(`key cannot be empty`);
value = newVal;
}
});
}
@Sealed
class UserService {
@Validate name: string;
@Log
createUser(name: string) { return { id: Date.now(), name }; }
}
```
**执行顺序(由下至上,由内至外):**
1. 参数装饰器
2. 方法/属性装饰器(从下到上)
3. 类装饰器
---
### TS-S-02 类型体操(Type Gymnastics)高频题
**难度:** 高级/专家
```typescript
// 1. 实现 TupleToUnion
type TupleToUnion<T extends any[]> = T[number];
type Colors = TupleToUnion<['red', 'green', 'blue']>; // 'red' | 'green' | 'blue'
// 2. 实现 UnionToIntersection
type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
// 3. 实现 IsNever
type IsNever<T> = [T] extends [never] ? true : false;
// 4. 实现链式调用类型
interface Chainable<Option = {}> {
option<K extends string, V>(
key: K,
value: V
): Chainable<Option & Record<K, V>>;
get(): Option;
}
// 5. 实现 Permutation(全排列)
type Permutation<T, K = T> =
[T] extends [never]
? []
: K extends K
? [K, ...Permutation<Exclude<T, K>>]
: never;
// 6. 递归类型 - 展平数组
type Flatten<T> = T extends (infer U)[]
? U extends any[]
? Flatten<U>
: U
: T;
```
---
## 专家
### TS-E-01 TypeScript 编译器与性能
**难度:** 专家
**tsconfig.json 关键配置:**
```json
{
"compilerOptions": {
"strict": true, // 开启所有严格检查
"noUncheckedIndexedAccess": true, // 索引访问包含 undefined
"exactOptionalPropertyTypes": true, // 可选属性不能设为 undefined
"isolatedModules": true, // 每个文件独立编译(Vite/SWC 要求)
"verbatimModuleSyntax": true, // 替代 importsNotUsedAsValues
"skipLibCheck": true, // 跳过 .d.ts 检查(加快编译)
"incremental": true // 增量编译
}
}
```
**性能优化策略:**
1. 使用 `isolatedModules` + SWC/esbuild 加速开发
2. `Project References` 实现大型项目模块化编译
3. 避免大量复杂的条件类型(编译期计算开销)
4. 使用 `@ts-ignore` / `@ts-expect-error` 处理已知问题点
**追问:** TypeScript 5.x 的重要新特性?
- `const` 类型参数(TS 5.0)
- `using` 声明(Explicit Resource Management)(TS 5.2)
- 装饰器规范(ES TC39 Stage 3)(TS 5.0)
- `satisfies` 操作符(TS 4.9)
FILE:references/vue.md
# Vue 深度面试题库
## 目录
- [初级](#初级)
- [中级](#中级)
- [高级](#高级)
- [专家](#专家)
---
## 初级
### V-J-01 Vue 2 vs Vue 3 核心区别
**难度:** 初级
| 特性 | Vue 2 | Vue 3 |
|------|-------|-------|
| 响应式 | `Object.defineProperty` | `Proxy` |
| API 风格 | Options API | Composition API(兼容 Options) |
| 根节点 | 单根节点 | 多根节点(Fragment) |
| 生命周期 | `beforeCreate/created/...` | `setup()` 替代前两者 |
| TypeScript | 二等公民 | 一等公民 |
| 性能 | — | 更快的渲染,更小的包体积 |
| Tree Shaking | 有限 | 完全支持 |
---
### V-J-02 v-show vs v-if
**难度:** 初级
- `v-if`:条件为 false 时,完全不渲染 DOM,有销毁/重建开销
- `v-show`:始终渲染,通过 `display: none` 切换,切换开销低
- **选择**:频繁切换用 v-show;初始条件不可能为真用 v-if
---
## 中级
### V-M-01 Vue 3 响应式原理深度解析
**难度:** 中级/高级
**考察点:** Proxy、依赖收集
**核心:基于 Proxy + Reflect**
```javascript
// 简化版响应式实现
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key); // 依赖收集
const res = Reflect.get(target, key, receiver);
// 深度响应式
return typeof res === 'object' ? reactive(res) : res;
},
set(target, key, value, receiver) {
const res = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return res;
},
deleteProperty(target, key) {
const res = Reflect.deleteProperty(target, key);
trigger(target, key);
return res;
}
});
}
// 依赖收集核心
const targetMap = new WeakMap();
let activeEffect = null;
function track(target, key) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, depsMap = new Map());
let dep = depsMap.get(key);
if (!dep) depsMap.set(key, dep = new Set());
dep.add(activeEffect);
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
depsMap?.get(key)?.forEach(effect => effect());
}
```
**Vue 2 响应式的局限(为什么 Vue 3 使用 Proxy):**
- `Object.defineProperty` 无法检测**新增/删除属性**(需要 `Vue.set/Vue.delete`)
- 无法检测**数组索引修改**和 `length` 变化(Vue 2 对数组方法做了 hack)
- Proxy 可拦截所有操作(包括 `in`、`delete`、迭代等)
---
### V-M-02 Composition API 核心
**难度:** 中级
**`ref` vs `reactive`:**
```javascript
// ref - 包装任意值为响应式,通过 .value 访问
const count = ref(0);
count.value++; // 修改
// 模板中自动解包:{{ count }}(不需要 .value)
// reactive - 对象/数组的深度响应式
const state = reactive({ name: 'Vue', version: 3 });
state.name = 'Vue3'; // 直接修改
// shallowRef / shallowReactive - 浅层响应式(性能优化)
const data = shallowRef({ list: [] });
data.value = { list: [1, 2] }; // 触发更新
data.value.list.push(3); // 不触发更新
// toRefs - 解构 reactive 时保持响应性
const { name, version } = toRefs(state);
```
**computed 与 watch:**
```javascript
// computed - 有缓存,依赖不变不重算
const doubled = computed(() => count.value * 2);
// watchEffect - 自动追踪依赖
const stop = watchEffect(() => {
console.log(count.value); // 依赖 count
});
stop(); // 停止监听
// watch - 显式指定依赖,可获取新旧值
watch(
() => state.name,
(newVal, oldVal) => { console.log(newVal, oldVal); },
{ immediate: true, deep: true, flush: 'post' }
);
```
---
### V-M-03 虚拟 DOM 与 Diff 算法(Vue 3)
**难度:** 中级/高级
**Vue 3 Diff 优化(相比 Vue 2):**
1. **静态提升(Static Hoisting)**:静态节点只创建一次,复用 VNode 引用
2. **补丁标志(Patch Flags)**:编译时标记哪些属性是动态的,运行时跳过静态比对
3. **块树(Block Tree)**:将动态节点收集到 Block 的 dynamicChildren 数组,Diff 只对比动态节点
4. **最长递增子序列(LIS)**:用于最小化 DOM 移动操作
```javascript
// 补丁标志示例(编译器生成)
createVNode("div", { class: "container" }, [
createVNode("span", null, name, PatchFlags.TEXT), // 动态文本
createVNode("div", { id: dynamicId }, null, PatchFlags.PROPS, ["id"]) // 动态属性
])
```
**Vue 3 Diff 流程(有 key 的列表):**
1. 头头对比,相同则 patch 并移动指针
2. 尾尾对比,相同则 patch 并移动指针
3. 旧节点遍历完:挂载剩余新节点
4. 新节点遍历完:卸载剩余旧节点
5. 乱序处理:建立 key → index Map,使用 LIS 最小化移动
---
## 高级
### V-S-01 Vue 3 编译器优化原理
**难度:** 高级
**模板编译阶段:**
```
源码模板
↓ Parse(解析)
AST(抽象语法树)
↓ Transform(转换/优化)
优化后 AST(静态分析、提升、Block 收集)
↓ Generate(代码生成)
渲染函数
```
**关键优化:**
- `v-if` 和 `v-for` 创建 Block 节点
- 静态子树提升(`hoistStatic`)
- 事件监听缓存(`cacheHandlers`)
---
### V-S-02 Pinia vs Vuex
**难度:** 高级
| 特性 | Vuex 4 | Pinia |
|------|--------|-------|
| TypeScript | 需要大量类型声明 | 原生支持,自动推导 |
| 模块 | `modules`(嵌套命名空间) | 多个独立 store |
| Mutations | 必须 | 不需要(直接修改 state) |
| DevTools | 支持 | 支持(更好) |
| SSR | 手动处理 | 内置支持 |
| 插件 | store plugins | store plugins |
**Pinia 示例:**
```javascript
// 定义 store
const useUserStore = defineStore('user', () => {
const user = ref(null);
const isLoggedIn = computed(() => !!user.value);
async function login(credentials) {
user.value = await api.login(credentials);
}
return { user, isLoggedIn, login };
});
// 使用
const userStore = useUserStore();
userStore.login({ ... });
```
---
## 专家
### V-E-01 Vue 3 渲染器与自定义渲染器
**难度:** 专家
```javascript
// 自定义渲染器(平台无关的 Vue 3)
import { createRenderer } from '@vue/runtime-core';
const { createApp } = createRenderer({
// 创建元素
createElement(type) { return canvas.createElement(type); },
// 插入元素
insert(el, parent, anchor) { parent.insertBefore(el, anchor); },
// 设置文本
setElementText(el, text) { el.textContent = text; },
// 属性 patch
patchProp(el, key, prevValue, nextValue) {
el[key] = nextValue;
},
// 创建文本节点
createText(text) { return { type: 'text', text }; },
// 其他必要操作...
});
// 可以渲染到 Canvas、Native、WebGL 等任意平台
```
**追问:** Vue 3 如何实现跨平台渲染?(运行时核心与平台无关,通过注入平台特定的 DOM 操作实现)
Micro-frontend architecture design and implementation guide. Use when: designing micro-frontend architecture, choosing between Module Federation/qiankun/sing...
---
name: micro-frontend-arch
description: "Micro-frontend architecture design and implementation guide. Use when: designing micro-frontend architecture, choosing between Module Federation/qiankun/single-spa/wujie, splitting monolith into sub-apps, implementing style isolation, setting up inter-app communication, or deploying micro-frontends independently."
---
# Micro-Frontend Architecture
## Framework Selection
| Scenario | Recommended |
|----------|-------------|
| New project, Webpack 5 | Module Federation |
| Legacy jQuery/Vue2 migration | qiankun |
| Multi-framework coexistence | single-spa |
| Need JS sandbox + style isolation | wujie |
## Core Workflow
1. **Design** → Define app boundaries, shared dependencies, routing strategy
2. **Setup** → Configure main app shell + sub-apps (see framework-specific refs)
3. **Communication** → Choose communication pattern (see references/communication.md)
4. **Style Isolation** → Prevent CSS conflicts (see references/style-isolation.md)
5. **Deploy** → Independent CI/CD per sub-app (see references/deployment.md)
## References
- **references/module-federation.md** — Webpack Module Federation config & patterns
- **references/qiankun.md** — qiankun registration, sandbox, lifecycle hooks
- **references/single-spa.md** — single-spa root config & app registration
- **references/communication.md** — Props, CustomEvent, shared store patterns
- **references/style-isolation.md** — Shadow DOM, CSS Modules, scoped strategies
- **references/deployment.md** — Independent deploy, CDN, versioning strategy
FILE:references/communication.md
# Inter-App Communication
## 1. CustomEvent (Loosely Coupled, No Dependencies)
```js
// Publisher (sub-app A or main app)
function publish(eventName, detail) {
window.dispatchEvent(new CustomEvent(eventName, { detail }));
}
publish('user:login', { userId: '123', name: 'Alice' });
// Subscriber (sub-app B)
function subscribe(eventName, handler) {
window.addEventListener(eventName, (e) => handler(e.detail));
return () => window.removeEventListener(eventName, handler); // cleanup
}
const unsub = subscribe('user:login', ({ userId }) => {
console.log('User logged in:', userId);
});
// On unmount: unsub()
```
## 2. Props Passing (Parent → Child, qiankun / Module Federation)
```js
// qiankun main app → sub-app via registerMicroApps props
// Already shown in qiankun.md; use for initial data + callbacks
// Module Federation: pass via React props normally
// Host:
<RemoteWidget onAction={handleAction} config={widgetConfig} />
// Limitation: only works for parent→child, not sibling communication
```
## 3. Shared State — mitt (Lightweight Event Bus)
```js
// shared/bus.js — loaded as a singleton (Module Federation shared or CDN)
import mitt from 'mitt';
export const bus = mitt();
// App A — emit
import { bus } from 'shared/bus';
bus.emit('cart:updated', { count: 5 });
// App B — listen
import { bus } from 'shared/bus';
bus.on('cart:updated', ({ count }) => updateCartBadge(count));
bus.on('*', (type, e) => console.log('global event', type, e)); // wildcard
// Cleanup on unmount
bus.off('cart:updated', handler);
```
## 4. Shared State — RxJS BehaviorSubject (Stateful)
```js
// shared/state.js
import { BehaviorSubject } from 'rxjs';
export const userState$ = new BehaviorSubject(null); // null = not logged in
// App A — update state
userState$.next({ id: '1', name: 'Alice', role: 'admin' });
// App B — subscribe (gets current value immediately on subscribe)
const sub = userState$.subscribe((user) => {
if (user) renderUserMenu(user);
});
// Cleanup
sub.unsubscribe();
```
## 5. localStorage / sessionStorage (Cross-Tab, Simple)
```js
// Write
localStorage.setItem('auth_token', JSON.stringify({ token, expiresAt }));
// Read from any sub-app
const auth = JSON.parse(localStorage.getItem('auth_token') ?? 'null');
// Listen for changes from OTHER tabs/windows
window.addEventListener('storage', (e) => {
if (e.key === 'auth_token') handleAuthChange(JSON.parse(e.newValue));
});
// Note: 'storage' event does NOT fire in the same tab that wrote the value
```
## Pattern Comparison
| Pattern | Direction | Real-time | Persistent |
|---------|-----------|-----------|------------|
| CustomEvent | Any | ✅ | ❌ |
| Props | Parent→Child | ✅ | ❌ |
| mitt bus | Any | ✅ | ❌ |
| RxJS Subject | Any | ✅ | ❌ |
| localStorage | Any | Cross-tab | ✅ |
FILE:references/deployment.md
# Deployment
## Independent Deploy — CI/CD per Sub-App
```yaml
# .github/workflows/deploy-app-orders.yml
on:
push:
paths: ['apps/app-orders/**'] # only trigger on changes to this sub-app
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd apps/app-orders && npm ci && npm run build
- name: Upload to CDN
run: aws s3 sync dist/ s3://my-mfe-cdn/app-orders/{ github.sha}/
- name: Update manifest
run: |
curl -X PATCH https://api.example.com/manifest \
-d '{"app-orders": "{ github.sha}"}'
```
## Dynamic Remote Entry (Runtime Version Resolution)
```js
// shell/src/remoteLoader.js — fetch active versions from manifest API
async function getRemoteUrl(appName) {
const manifest = await fetch('/api/mfe-manifest').then(r => r.json());
const hash = manifest[appName]; // e.g. "a1b2c3d"
return `https://cdn.example.com/appName/hash/remoteEntry.js`;
}
// Then load dynamically (see module-federation.md loadRemote)
const url = await getRemoteUrl('appOrders');
const { default: OrderList } = await loadRemote('appOrders', './OrderList', url);
```
## Manifest File Strategy
```json
// /api/mfe-manifest (served by main app or CDN)
{
"app-orders": "a1b2c3d4",
"app-user": "e5f6g7h8",
"app-dashboard": "i9j0k1l2",
"updatedAt": "2024-01-15T10:00:00Z"
}
```
## Versioning Strategy
| Approach | Pros | Cons |
|----------|------|------|
| Git SHA per deploy | Exact traceability | Long URLs |
| Semver tag | Human-readable | Manual tagging |
| `latest` alias (CDN) | Simple | Risky, cache issues |
Recommended: **Git SHA** in CDN path + **`latest` symlink** for fast fallback.
## Canary / Gray Release
```js
// Serve different versions to % of users based on cookie or user segment
function getRemoteVersion(appName, userId) {
const isCanary = hash(userId) % 100 < 10; // 10% canary
return isCanary
? `https://cdn.example.com/appName/canary/remoteEntry.js`
: `https://cdn.example.com/appName/stable/remoteEntry.js`;
}
```
## Rollback
```bash
# Rollback = update manifest to point to previous SHA
curl -X PATCH https://api.example.com/manifest \
-d '{"app-orders": "<previous-sha>"}'
# No redeploy needed — clients pick up new manifest on next load
```
FILE:references/module-federation.md
# Module Federation
## Host App (Main Shell) — webpack.config.js
```js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'shell',
remotes: {
// Static remote
appOrders: 'appOrders@http://localhost:3001/remoteEntry.js',
// Dynamic remote (loaded at runtime)
appUser: 'appUser@[window.remoteUserUrl]/remoteEntry.js',
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
'react-router-dom': { singleton: true },
},
}),
],
};
```
## Remote App — webpack.config.js
```js
new ModuleFederationPlugin({
name: 'appOrders',
filename: 'remoteEntry.js',
exposes: {
'./OrderList': './src/components/OrderList',
'./OrderDetail': './src/pages/OrderDetail',
'./store': './src/store/index', // share store if needed
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
},
}),
```
## Consuming a Remote in Host
```jsx
// Static import (webpack resolves at build time config)
import OrderList from 'appOrders/OrderList';
// Lazy + Suspense (recommended for code splitting)
const OrderDetail = React.lazy(() => import('appOrders/OrderDetail'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OrderDetail orderId="123" />
</Suspense>
);
}
```
## Dynamic Remote Loading (Runtime URL)
```js
// Load remote entry script dynamically, then use the module
async function loadRemote(scope, module, url) {
await __webpack_init_sharing__('default');
const container = window[scope]; // injected by remote script
await container.init(__webpack_share_scopes__.default);
const factory = await container.get(module);
return factory();
}
// Usage
const { default: Widget } = await loadRemote(
'appDashboard',
'./Widget',
'https://cdn.example.com/dashboard/remoteEntry.js'
);
```
## Shared Dependencies — Key Options
```js
shared: {
lodash: {
singleton: true, // only one instance allowed
eager: false, // lazy load (default, reduces initial bundle)
strictVersion: false, // warn but don't error on version mismatch
requiredVersion: '^4.0.0',
},
}
```
## TypeScript: Auto-generate Remote Types
```bash
# Use @module-federation/typescript or federation-dts
npx @module-federation/typescript
```
Add to `tsconfig.json`:
```json
{ "paths": { "appOrders/*": ["./node_modules/@mf-types/appOrders/*"] } }
```
FILE:references/qiankun.md
# qiankun
## Main App — Register Sub-Apps
```js
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'app-vue',
entry: '//localhost:8081', // sub-app dev server or CDN URL
container: '#sub-app-container', // mount point in main app HTML
activeRule: '/vue', // activate when route matches
props: { token: getToken(), bus: eventBus },
},
{
name: 'app-react',
entry: '//localhost:8082',
container: '#sub-app-container',
activeRule: (location) => location.pathname.startsWith('/react'),
props: { userInfo: store.state.user },
},
]);
start({
sandbox: {
strictStyleIsolation: false, // Shadow DOM isolation (may break some libs)
experimentalStyleIsolation: true, // Scoped CSS prefix (safer)
},
prefetch: 'all', // 'all' | true | false | string[]
});
```
## Sub-App Lifecycle Exports (Vue 3 example)
```js
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
let app = null;
// Dev mode: mount directly without qiankun
if (!window.__POWERED_BY_QIANKUN__) {
createApp(App).use(router).mount('#app');
}
export async function bootstrap() {
console.log('[app-vue] bootstrap');
}
export async function mount(props) {
const { container, token, bus } = props;
app = createApp(App);
app.provide('bus', bus);
app.provide('token', token);
app.use(router);
// Mount to qiankun's container, not #app (avoid conflicts)
app.mount(container ? container.querySelector('#app') : '#app');
}
export async function unmount() {
app.unmount();
app = null;
}
export async function update(props) {
// Called when props change (optional)
console.log('[app-vue] props updated', props);
}
```
## Sub-App Webpack Config Adjustments
```js
// vue.config.js / webpack.config.js
const { name } = require('./package.json');
module.exports = {
devServer: {
port: 8081,
headers: { 'Access-Control-Allow-Origin': '*' }, // CORS for main app
},
configureWebpack: {
output: {
library: `name-[name]`,
libraryTarget: 'umd',
chunkLoadingGlobal: `webpackJsonp_name`,
},
},
};
```
## Props Communication
```js
// Main app sends data via props
registerMicroApps([{
name: 'app-vue',
props: {
sharedData: reactive({}), // reactive object
onAction: (type, payload) => { // callback for sub → main
store.dispatch(type, payload);
},
},
}]);
// Sub-app receives in mount()
export async function mount({ onAction, sharedData }) {
app.provide('onAction', onAction);
app.provide('sharedData', sharedData);
}
```
## Sandbox Options
| Option | Effect |
|--------|--------|
| `sandbox: false` | No isolation (legacy mode) |
| `sandbox: true` | Snapshot sandbox (safe default) |
| `strictStyleIsolation: true` | Shadow DOM (breaks some CSS) |
| `experimentalStyleIsolation: true` | Prefixed scoped CSS (recommended) |
FILE:references/single-spa.md
# single-spa
## Root Config (Main Shell)
```js
// src/index.js
import { registerApplication, start } from 'single-spa';
registerApplication({
name: '@org/app-navbar',
app: () => import('@org/app-navbar'), // dynamic import or SystemJS
activeWhen: ['/'], // always active
});
registerApplication({
name: '@org/app-home',
app: () => System.import('@org/app-home'), // SystemJS for runtime loading
activeWhen: '/home',
});
registerApplication({
name: '@org/app-settings',
app: () => System.import('@org/app-settings'),
activeWhen: (location) => location.pathname.startsWith('/settings'),
customProps: { authToken: () => localStorage.getItem('token') },
});
start({ urlRerouteOnly: true }); // don't re-route on same URL
```
## import-map (SystemJS runtime loading)
```html
<!-- index.html -->
<script type="systemjs-importmap">
{
"imports": {
"@org/app-navbar": "https://cdn.example.com/navbar/main.js",
"@org/app-home": "https://cdn.example.com/home/main.js",
"react": "https://cdn.jsdelivr.net/npm/react@18/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.production.min.js"
}
}
</script>
<script src="https://cdn.jsdelivr.net/npm/systemjs/dist/system.min.js"></script>
```
## Sub-App Lifecycle (React)
```js
import React from 'react';
import ReactDOM from 'react-dom/client';
import singleSpaReact from 'single-spa-react'; // framework adapter
import App from './App';
const lifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
errorBoundary(err) { return <div>Error: {err.message}</div>; },
});
export const { bootstrap, mount, unmount } = lifecycles;
```
## Sub-App Lifecycle (Vue 3)
```js
import { createApp } from 'vue';
import singleSpaVue from 'single-spa-vue';
import App from './App.vue';
const vueLifecycles = singleSpaVue({
createApp,
appOptions: { render: () => h(App) },
handleInstance: (app) => { app.use(router); },
});
export const { bootstrap, mount, unmount } = vueLifecycles;
```
## Parcel (Framework-Agnostic Component)
```js
// Mount a parcel manually (not route-driven)
import { mountRootParcel } from 'single-spa';
import parcelConfig from '@org/some-widget';
const parcel = mountRootParcel(parcelConfig, {
domElement: document.getElementById('widget-slot'),
customProps: { color: 'blue' },
});
// Later: unmount when done
parcel.unmount();
```
## Framework Adapters
| Framework | Package |
|-----------|---------|
| React | `single-spa-react` |
| Vue 3 | `single-spa-vue` |
| Angular | `single-spa-angular` |
| Vanilla JS | Manual lifecycle export |
FILE:references/style-isolation.md
# Style Isolation
## 1. CSS Modules (Build-Time Scoping)
```css
/* Button.module.css */
.btn { background: blue; color: white; padding: 8px 16px; }
.btn:hover { background: darkblue; }
```
```jsx
// Button.jsx
import styles from './Button.module.css';
// Compiled class name: .btn → .Button_btn__xK2p1 (unique hash)
export function Button({ label }) {
return <button className={styles.btn}>{label}</button>;
}
```
Webpack config (already enabled in CRA/Vite by default for `*.module.css`):
```js
{ test: /\.module\.css$/, use: ['style-loader', { loader: 'css-loader',
options: { modules: { localIdentName: '[name]__[local]__[hash:5]' } } }] }
```
## 2. Shadow DOM (Hard Isolation)
```js
// Attach Shadow DOM to sub-app container
const container = document.getElementById('sub-app');
const shadow = container.attachShadow({ mode: 'open' });
// Inject styles scoped to shadow root
const style = document.createElement('style');
style.textContent = `.title { color: red; }`; // won't leak outside
shadow.appendChild(style);
// Mount React/Vue app into shadow root
ReactDOM.createRoot(shadow).render(<App />);
```
⚠️ **Caveats:** Portals (modals/tooltips), `document.querySelector`, and some
3rd-party libs break inside Shadow DOM. Use `experimentalStyleIsolation` in
qiankun as a safer alternative.
## 3. CSS Namespace / BEM Prefix (Lightweight)
```css
/* Sub-app wraps all styles under a unique prefix */
.app-orders .btn { background: blue; }
.app-orders .card { border-radius: 8px; }
```
```html
<!-- Sub-app root element carries the namespace class -->
<div id="app" class="app-orders">
<button class="btn">Order Now</button>
</div>
```
PostCSS plugin auto-prefix:
```js
// postcss.config.js
module.exports = { plugins: [require('postcss-prefix-selector')({
prefix: '.app-orders', exclude: ['.app-orders'] })] };
```
## 4. qiankun experimentalStyleIsolation
```js
// Automatically prefixes sub-app styles with div[data-qiankun="app-name"]
start({ sandbox: { experimentalStyleIsolation: true } });
// Generated: div[data-qiankun="app-vue"] .btn { ... }
```
## 5. Style Piercing (When You Need to Override)
```css
/* Vue: deep selector */
.parent :deep(.child-class) { color: red; }
/* Shadow DOM piercing (deprecated in modern browsers, avoid) */
/* Use CSS custom properties (variables) for theming instead */
:root { --btn-color: blue; }
.shadow-child { color: var(--btn-color); } /* vars DO cross shadow boundary */
```
## Summary
| Strategy | Isolation Level | Complexity | Compatibility |
|----------|----------------|------------|---------------|
| CSS Modules | Component-level | Low | High |
| BEM Prefix | App-level | Low | High |
| qiankun scoped | App-level | None | High |
| Shadow DOM | Hard boundary | Medium | Medium |
Comprehensive guide for building enterprise-grade component libraries and design systems from scratch. Use this skill when a frontend engineer needs to: (1)...
---
name: design-system-builder
description: >
Comprehensive guide for building enterprise-grade component libraries and design systems from scratch.
Use this skill when a frontend engineer needs to: (1) set up a component library monorepo with build tooling,
(2) establish a Design Token system (colors, typography, spacing, shadows), (3) define component development
standards (naming, Props API, TypeScript types, documentation), (4) configure Storybook for interactive docs,
(5) implement a theme system with dark mode support, (6) plan component testing strategy (visual regression,
interaction tests), or (7) set up a release pipeline (versioning, changelog, npm publishing).
Primary stack: React + TypeScript. Also covers Vue 3 patterns where applicable.
---
# Design System Builder
A structured guide for building production-ready component libraries and design systems. Covers architecture,
tokens, components, documentation, theming, testing, and publishing.
## Quick Decision Map
| Goal | Start here |
|---|---|
| Set up monorepo + build | [Architecture](#1-architecture--monorepo-setup) |
| Define design tokens | [references/tokens.md](references/tokens.md) |
| Build components | [references/component-patterns.md](references/component-patterns.md) |
| Storybook setup | [references/storybook-setup.md](references/storybook-setup.md) |
| Theming / dark mode | [references/theming.md](references/theming.md) |
| Testing strategy | [references/testing-strategy.md](references/testing-strategy.md) |
| Release pipeline | [references/release-pipeline.md](references/release-pipeline.md) |
---
## 1. Architecture & Monorepo Setup
### Recommended Structure
Use **pnpm workspaces** + **Turborepo** for monorepo management.
```
my-design-system/
├── packages/
│ ├── tokens/ # Design tokens (CSS vars, JS objects)
│ ├── icons/ # SVG icon components
│ ├── components/ # Core UI components
│ ├── themes/ # Theme definitions
│ └── utils/ # Shared utilities (cn, clsx, etc.)
├── apps/
│ └── docs/ # Storybook documentation site
├── package.json # Root workspace config
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.base.json
```
### Bootstrap Commands
```bash
# Initialize monorepo
mkdir my-design-system && cd my-design-system
pnpm init
pnpm add -D turbo -w
# Create workspace config
echo "packages:\n - 'packages/*'\n - 'apps/*'" > pnpm-workspace.yaml
```
**`turbo.json`** — task pipeline:
```json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"dev": { "cache": false, "persistent": true },
"test": { "dependsOn": ["^build"] },
"lint": {}
}
}
```
### Build Tooling Choice
| Tool | Best for | Notes |
|---|---|---|
| **Vite + vite-plugin-dts** | React/Vue components | Fast, ESM-first |
| **tsup** | Utility packages | Zero-config, dual CJS/ESM |
| **Rollup** | Fine-grained control | More config overhead |
**Recommended `packages/components/package.json`:**
```json
{
"name": "@myds/components",
"version": "0.1.0",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch"
}
}
```
---
## 2. Design Tokens
Design tokens are the single source of truth for visual decisions.
> **Read [references/tokens.md](references/tokens.md)** for the complete token schema, naming conventions,
> CSS variable patterns, and multi-brand token examples.
### Quick Start
```bash
# Install Style Dictionary (token transformation tool)
pnpm add -D style-dictionary -w
```
Token files live in `packages/tokens/src/`. Style Dictionary transforms them to CSS variables, JS/TS constants,
and platform-specific outputs (iOS, Android).
Core token categories: `color`, `typography`, `spacing`, `border-radius`, `shadow`, `z-index`, `motion`.
---
## 3. Component Development Standards
Every component should follow consistent conventions for long-term maintainability.
> **Read [references/component-patterns.md](references/component-patterns.md)** for detailed patterns:
> file structure, Props API design, compound components, polymorphic components, accessibility requirements,
> and documentation templates.
### Non-Negotiable Rules
1. **TypeScript first** — all props typed with explicit interfaces, no `any`
2. **`forwardRef`** for all leaf elements (React)
3. **`aria-*` attributes** — never ship an inaccessible component
4. **Controlled + Uncontrolled** — support both patterns for form components
5. **`data-testid`** — include for E2E testability
### Component File Structure
```
Button/
├── Button.tsx # Component implementation
├── Button.types.ts # Props interface & type exports
├── Button.test.tsx # Unit + interaction tests
├── Button.stories.tsx # Storybook stories
└── index.ts # Public barrel export
```
---
## 4. Storybook
Storybook is the primary documentation and development environment.
> **Read [references/storybook-setup.md](references/storybook-setup.md)** for full configuration:
> addon setup, autodocs, MDX pages, controls, theming the Storybook UI, and deployment.
### Bootstrap
```bash
cd apps/docs
pnpm dlx storybook@latest init
# Select React + Vite when prompted
```
Essential addons:
- `@storybook/addon-essentials` (controls, actions, docs, viewport)
- `@storybook/addon-a11y` (accessibility audit)
- `@storybook/addon-themes` (theme switching)
- `storybook-addon-pseudo-states` (hover/focus/active states)
---
## 5. Theme System
> **Read [references/theming.md](references/theming.md)** for full theming architecture:
> CSS custom properties strategy, dark mode implementation (media query vs. class-based),
> ThemeProvider pattern for React, and Vue 3 provide/inject approach.
### Core Concept
Tokens define **semantic aliases** that point to primitive values:
```css
/* Primitive */
--color-blue-500: #3b82f6;
/* Semantic (theme-aware) */
[data-theme="light"] { --color-primary: var(--color-blue-500); }
[data-theme="dark"] { --color-primary: var(--color-blue-400); }
```
Components reference semantic tokens only — never primitives directly.
---
## 6. Testing Strategy
> **Read [references/testing-strategy.md](references/testing-strategy.md)** for the full pyramid:
> unit tests (Vitest), interaction tests (Testing Library), visual regression (Chromatic/Percy),
> and accessibility automation.
### Test Pyramid for Component Libraries
```
[Visual Regression] ← Chromatic / Percy
[Interaction Tests] ← @testing-library/react
[Unit / Logic Tests] ← Vitest
```
**Minimum bar per component:**
- Renders without errors
- Props produce expected output
- Interactive states (hover, focus, disabled) work
- No critical a11y violations (axe-core)
---
## 7. Release Pipeline
> **Read [references/release-pipeline.md](references/release-pipeline.md)** for the complete release flow:
> Changesets setup, versioning strategy, automated changelog, CI/CD pipeline, and npm publishing.
### Tool: Changesets
```bash
pnpm add -D @changesets/cli -w
pnpm changeset init
```
Workflow:
1. `pnpm changeset` — create a changeset (describe changes)
2. `pnpm changeset version` — bump versions + update CHANGELOG.md
3. `pnpm changeset publish` — publish to npm
---
## Vue 3 Notes
Most patterns apply to Vue 3 with minor adaptations:
- Props: use `defineProps<T>()` with TypeScript generics
- `expose()` replaces React's `forwardRef`
- Theme injection: `provide/inject` replaces React Context
- Testing: `@vue/test-utils` + Vitest
- See [references/component-patterns.md](references/component-patterns.md) for Vue-specific examples
---
## Recommended Tech Stack Summary
| Layer | React | Vue 3 |
|---|---|---|
| Monorepo | pnpm + Turborepo | pnpm + Turborepo |
| Build | tsup / Vite | tsup / Vite |
| Tokens | Style Dictionary | Style Dictionary |
| Docs | Storybook 8 | Storybook 8 |
| Unit tests | Vitest + Testing Library | Vitest + @vue/test-utils |
| Visual regression | Chromatic | Chromatic |
| Release | Changesets | Changesets |
| CSS | CSS Modules / CSS-in-JS | CSS Modules / scoped SFC |
FILE:references/component-patterns.md
# Component Patterns Reference
## Table of Contents
1. [File Structure](#file-structure)
2. [Naming Conventions](#naming-conventions)
3. [Props API Design](#props-api-design)
4. [TypeScript Patterns](#typescript-patterns)
5. [Compound Components](#compound-components)
6. [Polymorphic Components](#polymorphic-components)
7. [Accessibility Requirements](#accessibility-requirements)
8. [Documentation Template](#documentation-template)
9. [Vue 3 Patterns](#vue-3-patterns)
---
## File Structure
```
packages/components/src/
├── Button/
│ ├── Button.tsx
│ ├── Button.types.ts
│ ├── Button.test.tsx
│ ├── Button.stories.tsx
│ └── index.ts
├── Input/
│ └── ...
└── index.ts # Root barrel — exports everything
```
**`index.ts` (component barrel):**
```ts
export { Button } from './Button';
export type { ButtonProps } from './Button';
```
---
## Naming Conventions
| What | Convention | Example |
|---|---|---|
| Component file | PascalCase | `Button.tsx` |
| Component name | PascalCase | `export const Button` |
| Props interface | `{Name}Props` | `ButtonProps` |
| Variants | string union | `'primary' \| 'secondary' \| 'ghost'` |
| Size prop | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl'` |
| State props | semantic booleans | `isDisabled`, `isLoading`, `isSelected` |
| Event handlers | `on{Event}` | `onClick`, `onChange`, `onDismiss` |
| CSS classes | `ds-{component}-{element}` | `ds-button-icon` |
| Data attributes | `data-{state}` | `data-loading`, `data-variant` |
---
## Props API Design
### Core Principles
1. **Extend native HTML element props** — spread `React.HTMLAttributes` or element-specific types
2. **Use string unions over enums** — better ergonomics and tree-shaking
3. **Provide sensible defaults** — `size="md"`, `variant="primary"`
4. **`className` always accepted** — never block it
5. **`children` explicit** — include in interface, not just implicit
6. **Event naming** — follow React conventions (`onClick`, not `handleClick`)
### Button Example (React)
```tsx
// Button.types.ts
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Visual style variant */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
/** Size of the button */
size?: 'sm' | 'md' | 'lg';
/** Show loading spinner and disable interaction */
isLoading?: boolean;
/** Render as full-width block element */
isFullWidth?: boolean;
/** Icon to render before the label */
leftIcon?: React.ReactElement;
/** Icon to render after the label */
rightIcon?: React.ReactElement;
/** Accessible label when using icon-only */
'aria-label'?: string;
}
```
```tsx
// Button.tsx
import * as React from 'react';
import type { ButtonProps } from './Button.types';
import { cn } from '../utils/cn';
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
variant = 'primary',
size = 'md',
isLoading = false,
isFullWidth = false,
leftIcon,
rightIcon,
children,
className,
disabled,
...props
},
ref
) => {
const isDisabled = disabled || isLoading;
return (
<button
ref={ref}
disabled={isDisabled}
data-variant={variant}
data-size={size}
data-loading={isLoading || undefined}
aria-busy={isLoading}
className={cn(
'ds-button',
`ds-button--variant`,
`ds-button--size`,
isFullWidth && 'ds-button--full-width',
className
)}
{...props}
>
{isLoading && <span className="ds-button__spinner" aria-hidden="true" />}
{leftIcon && <span className="ds-button__icon ds-button__icon--left">{leftIcon}</span>}
{children && <span className="ds-button__label">{children}</span>}
{rightIcon && <span className="ds-button__icon ds-button__icon--right">{rightIcon}</span>}
</button>
);
}
);
Button.displayName = 'Button';
```
### Form Input Example
```tsx
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
label?: string;
helperText?: string;
errorMessage?: string;
isRequired?: boolean;
isInvalid?: boolean;
size?: 'sm' | 'md' | 'lg';
leftAddon?: React.ReactNode;
rightAddon?: React.ReactNode;
}
```
---
## TypeScript Patterns
### Discriminated Unions for Variants
```ts
// Icon button requires aria-label; text button requires children
type ButtonWithLabel = {
children: React.ReactNode;
'aria-label'?: string;
};
type IconOnlyButton = {
children?: never;
'aria-label': string;
};
export type ButtonProps = (ButtonWithLabel | IconOnlyButton) & BaseButtonProps;
```
### Generic Components
```ts
// Select component with typed value
interface SelectProps<T extends string | number> {
value: T;
onChange: (value: T) => void;
options: Array<{ label: string; value: T }>;
}
```
### `cn` Utility (className merging)
```ts
// packages/utils/src/cn.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
```
---
## Compound Components
Use compound components for complex UI with multiple related parts.
```tsx
// Dialog compound component
interface DialogContextValue {
isOpen: boolean;
onClose: () => void;
}
const DialogContext = React.createContext<DialogContextValue | null>(null);
function useDialogContext() {
const ctx = React.useContext(DialogContext);
if (!ctx) throw new Error('Dialog subcomponent used outside <Dialog>');
return ctx;
}
// Root
export function Dialog({ children, isOpen, onClose }: DialogRootProps) {
return (
<DialogContext.Provider value={{ isOpen, onClose }}>
{children}
</DialogContext.Provider>
);
}
// Subcomponents
Dialog.Trigger = function DialogTrigger({ children }: { children: React.ReactNode }) {
const { onClose } = useDialogContext();
return <button onClick={onClose}>{children}</button>;
};
Dialog.Content = function DialogContent({ children }: { children: React.ReactNode }) {
const { isOpen } = useDialogContext();
if (!isOpen) return null;
return <div role="dialog" aria-modal>{children}</div>;
};
Dialog.Title = function DialogTitle({ children }: { children: React.ReactNode }) {
return <h2>{children}</h2>;
};
// Usage:
// <Dialog isOpen={open} onClose={() => setOpen(false)}>
// <Dialog.Trigger>Open</Dialog.Trigger>
// <Dialog.Content>
// <Dialog.Title>Hello</Dialog.Title>
// </Dialog.Content>
// </Dialog>
```
---
## Polymorphic Components
Allow components to render as different HTML elements or components.
```tsx
type AsProp<C extends React.ElementType> = {
as?: C;
};
type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);
type PolymorphicComponentProp<
C extends React.ElementType,
Props = {}
> = React.PropsWithChildren<Props & AsProp<C>> &
Omit<React.ComponentPropsWithRef<C>, PropsToOmit<C, Props>>;
// Text component that can render as any element
interface TextOwnProps {
size?: 'sm' | 'md' | 'lg';
weight?: 'regular' | 'medium' | 'bold';
color?: string;
}
type TextProps<C extends React.ElementType = 'span'> = PolymorphicComponentProp<C, TextOwnProps>;
export function Text<C extends React.ElementType = 'span'>({
as,
size = 'md',
weight = 'regular',
color,
children,
className,
...props
}: TextProps<C>) {
const Component = as || 'span';
return (
<Component className={cn('ds-text', `ds-text--size`, className)} {...props}>
{children}
</Component>
);
}
// Usage:
// <Text as="h1" size="lg" weight="bold">Heading</Text>
// <Text as="p">Paragraph</Text>
// <Text as={Link} href="/about">Link text</Text>
```
---
## Accessibility Requirements
Every component must meet WCAG 2.1 AA.
### Checklist
- [ ] **Keyboard navigation** — all interactive elements reachable and operable via keyboard
- [ ] **Focus management** — visible focus ring, correct tab order
- [ ] **ARIA roles** — correct semantic role (`button`, `dialog`, `menu`, etc.)
- [ ] **ARIA labels** — `aria-label` or `aria-labelledby` for unlabeled elements
- [ ] **ARIA states** — `aria-expanded`, `aria-selected`, `aria-disabled`, `aria-checked` as appropriate
- [ ] **Color contrast** — minimum 4.5:1 for normal text, 3:1 for large text
- [ ] **No color-only information** — don't use color as the sole error indicator
- [ ] **Motion** — respect `prefers-reduced-motion`
- [ ] **Screen reader testing** — test with VoiceOver (Mac) + NVDA (Windows)
### Reduced Motion Pattern
```css
@media (prefers-reduced-motion: reduce) {
.ds-button__spinner {
animation: none;
}
.ds-transition {
transition: none !important;
}
}
```
---
## Documentation Template
Every component's Storybook `stories.tsx` should include:
```tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'], // enables auto-generated docs page
parameters: {
docs: {
description: {
component: 'Triggers an action or navigates. Use for primary user actions.',
},
},
},
argTypes: {
variant: {
control: 'select',
description: 'Visual style of the button',
},
size: {
control: 'select',
},
isLoading: {
control: 'boolean',
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
// Default story (shown in docs as the primary example)
export const Default: Story = {
args: {
children: 'Button',
variant: 'primary',
size: 'md',
},
};
// All variants
export const Variants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '8px' }}>
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>
</div>
),
};
// Loading state
export const Loading: Story = {
args: { children: 'Saving...', isLoading: true },
};
// Disabled state
export const Disabled: Story = {
args: { children: 'Disabled', disabled: true },
};
```
---
## Vue 3 Patterns
### Props with TypeScript
```vue
<!-- Button.vue -->
<script setup lang="ts">
interface Props {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
isDisabled?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
isLoading: false,
isDisabled: false,
});
const emit = defineEmits<{
click: [event: MouseEvent];
}>();
// Expose ref for parent access
defineExpose({ focus: () => buttonRef.value?.focus() });
</script>
<template>
<button
:class="['ds-button', `ds-button--variant`, `ds-button--size`]"
:disabled="isDisabled || isLoading"
:aria-busy="isLoading"
@click="emit('click', $event)"
>
<slot />
</button>
</template>
```
### Provide/Inject for Context
```ts
// composables/useFormContext.ts
import { provide, inject, type InjectionKey } from 'vue';
interface FormContext {
isDisabled: Ref<boolean>;
size: Ref<'sm' | 'md' | 'lg'>;
}
const FormContextKey: InjectionKey<FormContext> = Symbol('FormContext');
export function provideFormContext(ctx: FormContext) {
provide(FormContextKey, ctx);
}
export function useFormContext() {
return inject(FormContextKey);
}
```
FILE:references/release-pipeline.md
# Release Pipeline Reference
## Table of Contents
1. [Tool: Changesets](#tool-changesets)
2. [Versioning Strategy](#versioning-strategy)
3. [Workflow: Day-to-Day](#workflow-day-to-day)
4. [Automated CI/CD Pipeline](#automated-cicd-pipeline)
5. [npm Publishing](#npm-publishing)
6. [Package Configuration Checklist](#package-configuration-checklist)
7. [Changelog Format](#changelog-format)
8. [Pre-releases & Beta Versions](#pre-releases--beta-versions)
---
## Tool: Changesets
[Changesets](https://github.com/changesets/changesets) manages version bumps, changelog generation, and publishing in a monorepo. It's the standard tool for component library release workflows.
### Why Changesets?
- Works natively with pnpm/yarn workspaces
- Changelog is auto-generated from changeset descriptions
- Supports pre-releases and snapshot versions
- GitHub bot for PR-based reviews
- Atomic publish across multiple packages
### Installation
```bash
pnpm add -D @changesets/cli -w
pnpm changeset init
```
This creates `.changeset/config.json`.
**`.changeset/config.json`:**
```json
{
"$schema": "https://unpkg.com/@changesets/config/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
```
---
## Versioning Strategy
Follow **Semantic Versioning** (semver):
| Change Type | Version Bump | When |
|---|---|---|
| `patch` | `1.0.0 → 1.0.1` | Bug fixes, internal refactors |
| `minor` | `1.0.0 → 1.1.0` | New features, new components, new props (backward-compatible) |
| `major` | `1.0.0 → 2.0.0` | Breaking changes (removed props, renamed APIs, behavior changes) |
**Breaking change examples:**
- Removing or renaming a prop
- Changing a prop's type in a non-backward-compatible way
- Removing a component
- Changing the CSS class naming scheme
- Dropping support for a React version
---
## Workflow: Day-to-Day
### Step 1: Create a Changeset (when making changes)
```bash
pnpm changeset
```
Interactive prompts:
1. Select which packages changed
2. Select bump type (major / minor / patch)
3. Write a description (this becomes the changelog entry)
A markdown file is created in `.changeset/`:
```markdown
---
"@myds/components": minor
"@myds/tokens": patch
---
Add new `Badge` component with `status` and `size` variants.
Update color tokens with additional gray-950 step.
```
**Commit this file** with your PR.
### Step 2: Version Bump (before releasing)
```bash
# Consumes all pending .changeset files,
# bumps package versions, updates CHANGELOG.md files
pnpm changeset version
```
Review and commit the version bump:
```bash
git add .
git commit -m "chore: version packages"
```
### Step 3: Publish to npm
```bash
pnpm changeset publish
```
This publishes all packages that have version bumps. After publishing:
```bash
git push --follow-tags
```
---
## Automated CI/CD Pipeline
Use the official [changesets/action](https://github.com/changesets/action) for GitHub Actions.
### How It Works
1. PRs: Changeset bot comments to remind contributors to add changesets
2. On merge to `main`: A "Version Packages" PR is auto-created/updated
3. On merge of the version PR: packages are auto-published to npm
```yaml
# .github/workflows/release.yml
name: Release
on:
push:
branches:
- main
concurrency: { github.workflow}-{ github.ref}
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm test
- name: Create Release PR or Publish
uses: changesets/action@v1
with:
publish: pnpm changeset publish
version: pnpm changeset version
commit: "chore: version packages"
title: "chore: version packages"
env:
GITHUB_TOKEN: { secrets.GITHUB_TOKEN}
NPM_TOKEN: { secrets.NPM_TOKEN}
NODE_AUTH_TOKEN: { secrets.NPM_TOKEN}
```
---
## npm Publishing
### Before First Publish
1. **Create npm organization** (for scoped packages `@myds/*`):
```bash
npm org create myds
```
2. **Set up npm token**:
```bash
npm token create --type=automation
# Add as NPM_TOKEN secret in GitHub repo settings
```
3. **Ensure package.json is correct**:
```json
{
"name": "@myds/components",
"version": "0.1.0",
"private": false,
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
}
}
```
4. **Add `.npmignore`** (or use `files` field in package.json):
```
# .npmignore
src/
*.test.*
*.stories.*
tsconfig.json
vitest.config.ts
```
Or use `files` in `package.json` (preferred):
```json
{
"files": ["dist", "README.md"]
}
```
### Dry Run (check what will be published)
```bash
pnpm publish --dry-run
# or
npm pack --dry-run
```
---
## Package Configuration Checklist
Before first publish, verify each package:
- [ ] `name` is scoped and unique (`@myds/components`)
- [ ] `version` starts at `0.1.0` or `1.0.0`
- [ ] `main` points to CJS output
- [ ] `module` points to ESM output
- [ ] `types` points to `.d.ts`
- [ ] `exports` map defined for named sub-paths
- [ ] `files` field restricts to `dist/`
- [ ] `sideEffects: false` (for tree-shaking) — unless you have global CSS imports
- [ ] `peerDependencies` lists React (not in `dependencies`)
- [ ] `publishConfig.access: "public"` for scoped packages
**Full `package.json` example:**
```json
{
"name": "@myds/components",
"version": "1.0.0",
"description": "Enterprise UI component library",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"sideEffects": false,
"files": ["dist", "README.md"],
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./styles": "./dist/styles.css"
},
"peerDependencies": {
"react": ">=17",
"react-dom": ">=17"
},
"publishConfig": {
"access": "public"
},
"keywords": ["design-system", "react", "ui", "components"]
}
```
---
## Changelog Format
Changesets generates changelogs in this format:
```markdown
# @myds/components
## 2.1.0
### Minor Changes
- a1b2c3d: Add new `Badge` component with `status` and `size` variants
### Patch Changes
- Updated dependencies
- @myds/[email protected]
## 2.0.0
### Major Changes
- f4e5d6c: **Breaking:** `Button` prop `kind` renamed to `variant` for consistency
**Migration:** Replace all `kind="..."` with `variant="..."`
## 1.0.1
### Patch Changes
- b7c8d9e: Fix `Input` placeholder color in dark mode
```
**Writing good changeset descriptions:**
- Use present tense: "Add", "Fix", "Update", not "Added"
- Include migration guides for breaking changes
- Reference issue/PR numbers when helpful
- Keep summaries concise; add detail in PR body
---
## Pre-releases & Beta Versions
For releasing unstable/experimental versions:
```bash
# Enter pre-release mode
pnpm changeset pre enter beta
# Create changesets and version as normal
pnpm changeset
pnpm changeset version
# → versions become 2.0.0-beta.0, 2.0.0-beta.1, etc.
# Publish with beta tag
pnpm changeset publish --tag beta
# Exit pre-release mode when stable
pnpm changeset pre exit
```
Install beta version:
```bash
pnpm add @myds/components@beta
```
### Snapshot Releases (for PR testing)
```bash
# Publish a snapshot version for testing
pnpm changeset version --snapshot pr-123
pnpm changeset publish --tag snapshot
# → @myds/[email protected]
```
FILE:references/storybook-setup.md
# Storybook Setup Reference
## Table of Contents
1. [Installation & Bootstrap](#installation--bootstrap)
2. [Configuration Files](#configuration-files)
3. [Essential Addons](#essential-addons)
4. [Writing Stories](#writing-stories)
5. [Autodocs](#autodocs)
6. [MDX Documentation Pages](#mdx-documentation-pages)
7. [Theme Switcher Integration](#theme-switcher-integration)
8. [Deployment](#deployment)
---
## Installation & Bootstrap
```bash
cd apps/docs
# Bootstrap Storybook (choose React + Vite)
pnpm dlx storybook@latest init
# Add essential addons
pnpm add -D \
@storybook/addon-essentials \
@storybook/addon-a11y \
@storybook/addon-themes \
storybook-addon-pseudo-states
```
**Recommended Storybook version:** 8.x
---
## Configuration Files
### `.storybook/main.ts`
```ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: [
'../../packages/components/src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
'../../packages/*/src/**/*.stories.@(js|jsx|ts|tsx|mdx)',
'./src/**/*.mdx',
],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-themes',
'storybook-addon-pseudo-states',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
docs: {
autodocs: 'tag', // generate docs for stories with tags: ['autodocs']
},
typescript: {
reactDocgen: 'react-docgen-typescript',
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) =>
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
},
},
};
export default config;
```
### `.storybook/preview.ts`
```ts
import type { Preview } from '@storybook/react';
import '@myds/tokens/dist/tokens.css'; // load design tokens
import './preview.css'; // global storybook styles
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
layout: 'centered',
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: 'var(--ds-color-neutral-bg-base)' },
{ name: 'dark', value: '#0f172a' },
{ name: 'white', value: '#ffffff' },
],
},
a11y: {
config: {
rules: [{ id: 'color-contrast', enabled: true }],
},
},
},
globalTypes: {
theme: {
description: 'Global theme for components',
defaultValue: 'light',
toolbar: {
title: 'Theme',
icon: 'circlehollow',
items: ['light', 'dark'],
dynamicTitle: true,
},
},
},
decorators: [
(Story, context) => {
const theme = context.globals.theme || 'light';
return (
<div data-theme={theme} style={{ padding: '1rem' }}>
<Story />
</div>
);
},
],
};
export default preview;
```
---
## Essential Addons
| Addon | Purpose |
|---|---|
| `addon-essentials` | Controls, Actions, Docs, Viewport, Backgrounds, Toolbars |
| `addon-a11y` | axe-core accessibility audit panel |
| `addon-themes` | Theme switching toolbar |
| `storybook-addon-pseudo-states` | Force CSS pseudo-states (`:hover`, `:focus`, `:active`) |
### Addon Details
**Controls** — automatically generates UI controls from prop types. Works best with TypeScript + `react-docgen-typescript`.
**Actions** — logs event handler calls. Auto-configured for props matching `on[A-Z].*`.
**Viewport** — switch between device sizes. Add custom breakpoints:
```ts
// preview.ts
parameters: {
viewport: {
viewports: {
mobile: { name: 'Mobile', styles: { width: '390px', height: '844px' } },
tablet: { name: 'Tablet', styles: { width: '768px', height: '1024px' } },
desktop: { name: 'Desktop', styles: { width: '1440px', height: '900px' } },
},
},
}
```
---
## Writing Stories
### CSF3 Format (recommended)
```tsx
import type { Meta, StoryObj } from '@storybook/react';
import { within, userEvent, expect } from '@storybook/test';
import { Button } from '@myds/components';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
};
export default meta;
type Story = StoryObj<typeof Button>;
// ✅ Basic story
export const Primary: Story = {
args: {
children: 'Click me',
variant: 'primary',
},
};
// ✅ Story with play function (interaction test)
export const ClickTest: Story = {
args: { children: 'Submit', onClick: fn() },
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
await userEvent.click(canvas.getByRole('button'));
await expect(args.onClick).toHaveBeenCalledOnce();
},
};
// ✅ Render function story (multiple variants)
export const AllVariants: Story = {
render: () => (
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
{(['primary', 'secondary', 'ghost', 'danger'] as const).map((v) => (
<Button key={v} variant={v}>{v}</Button>
))}
</div>
),
};
```
### Story Organization
Use the `title` field to build a navigation tree:
```
Components/
Button
Input
Select
Feedback/
Alert
Toast
Badge
Navigation/
Tabs
Breadcrumb
Pagination
Layout/
Card
Divider
Stack
```
---
## Autodocs
Enable per-component with `tags: ['autodocs']` in the meta.
What autodocs generates:
- Props table from TypeScript types + JSDoc comments
- Live interactive example using `args`
- All named exports as story previews
**Enhance props documentation with JSDoc:**
```tsx
export interface ButtonProps {
/**
* Visual style variant of the button.
* @default 'primary'
*/
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
/**
* When true, shows a loading spinner and prevents interaction.
* @default false
*/
isLoading?: boolean;
}
```
---
## MDX Documentation Pages
Use MDX for design guidelines, overview pages, and token documentation.
**`apps/docs/src/introduction.mdx`:**
```mdx
import { Meta } from '@storybook/blocks';
<Meta title="Introduction" />
# My Design System
Built with React + TypeScript.
## Getting Started
\`\`\`bash
npm install @myds/components @myds/tokens
\`\`\`
Import the tokens CSS in your app entry:
\`\`\`ts
import '@myds/tokens/dist/tokens.css';
\`\`\`
```
**`apps/docs/src/tokens/colors.mdx`:**
```mdx
import { Meta, ColorPalette, ColorItem } from '@storybook/blocks';
<Meta title="Tokens/Colors" />
# Color Tokens
<ColorPalette>
<ColorItem
title="Primary"
subtitle="Used for primary actions"
colors={{
Default: 'var(--ds-color-primary-default)',
Hover: 'var(--ds-color-primary-hover)',
}}
/>
</ColorPalette>
```
---
## Theme Switcher Integration
With `@storybook/addon-themes`, wrap your theme provider:
```ts
// preview.ts
import { withThemeByDataAttribute } from '@storybook/addon-themes';
export const decorators = [
withThemeByDataAttribute({
themes: { light: 'light', dark: 'dark' },
defaultTheme: 'light',
attributeName: 'data-theme',
}),
];
```
---
## Deployment
### Static Build
```bash
pnpm build-storybook
# Output: storybook-static/
```
### Chromatic (Recommended)
Chromatic provides visual regression + hosted Storybook:
```bash
pnpm add -D chromatic
# Publish
npx chromatic --project-token=<token>
```
**GitHub Actions integration:**
```yaml
# .github/workflows/chromatic.yml
name: Chromatic
on: [push]
jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm build
- uses: chromaui/action@latest
with:
projectToken: { secrets.CHROMATIC_PROJECT_TOKEN}
workingDir: apps/docs
```
### Vercel / Netlify
Point the build command to `pnpm build-storybook` and publish dir to `storybook-static`.
FILE:references/testing-strategy.md
# Testing Strategy Reference
## Table of Contents
1. [Testing Pyramid](#testing-pyramid)
2. [Setup: Vitest + Testing Library](#setup-vitest--testing-library)
3. [Unit & Interaction Tests](#unit--interaction-tests)
4. [Storybook Interaction Tests](#storybook-interaction-tests)
5. [Visual Regression with Chromatic](#visual-regression-with-chromatic)
6. [Accessibility Testing](#accessibility-testing)
7. [Coverage Requirements](#coverage-requirements)
8. [CI Pipeline Integration](#ci-pipeline-integration)
---
## Testing Pyramid
```
┌─────────────────────────────┐
│ Visual Regression │ Chromatic / Percy
│ (screenshot diffing) │ ~Few per component
├─────────────────────────────┤
│ Interaction Tests │ @testing-library or
│ (user events, flows) │ Storybook play()
├─────────────────────────────┤
│ Unit / Logic Tests │ Vitest
│ (props, states, utils) │ ~Many per component
└─────────────────────────────┘
```
**Minimum per component:**
- [ ] Renders without errors
- [ ] Default props match snapshot/expectation
- [ ] Key prop variations produce correct output
- [ ] Interactive states (click, focus, disable) work
- [ ] No critical a11y violations
---
## Setup: Vitest + Testing Library
```bash
# Install
pnpm add -D \
vitest \
@vitest/ui \
jsdom \
@testing-library/react \
@testing-library/user-event \
@testing-library/jest-dom \
jest-axe \
-w
```
**`packages/components/vitest.config.ts`:**
```ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test-setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
thresholds: { lines: 80, functions: 80, branches: 70 },
},
},
});
```
**`src/test-setup.ts`:**
```ts
import '@testing-library/jest-dom';
import { expect } from 'vitest';
import { toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
```
---
## Unit & Interaction Tests
### Button Test Example
```tsx
// Button.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { axe } from 'jest-axe';
import { vi, describe, it, expect } from 'vitest';
import { Button } from './Button';
describe('Button', () => {
// Smoke test
it('renders without crashing', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toBeInTheDocument();
});
// Content
it('renders children as button label', () => {
render(<Button>Submit</Button>);
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
});
// Variants
it.each(['primary', 'secondary', 'ghost', 'danger'] as const)(
'renders %s variant without errors',
(variant) => {
render(<Button variant={variant}>Button</Button>);
expect(screen.getByRole('button')).toHaveAttribute('data-variant', variant);
}
);
// Disabled state
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Button</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
// Loading state
it('is disabled and aria-busy when isLoading', () => {
render(<Button isLoading>Button</Button>);
const button = screen.getByRole('button');
expect(button).toBeDisabled();
expect(button).toHaveAttribute('aria-busy', 'true');
});
// Click interaction
it('calls onClick when clicked', async () => {
const user = userEvent.setup();
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Button</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
// Does not fire click when disabled
it('does not call onClick when disabled', async () => {
const user = userEvent.setup();
const handleClick = vi.fn();
render(<Button disabled onClick={handleClick}>Button</Button>);
await user.click(screen.getByRole('button'));
expect(handleClick).not.toHaveBeenCalled();
});
// Ref forwarding
it('forwards ref to button element', () => {
const ref = { current: null };
render(<Button ref={ref}>Button</Button>);
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
});
// Accessibility
it('has no accessibility violations', async () => {
const { container } = render(<Button>Accessible button</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
```
### Input Test Example
```tsx
describe('Input', () => {
it('updates value on user input', async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(<Input label="Name" onChange={handleChange} />);
await user.type(screen.getByLabelText('Name'), 'John');
expect(handleChange).toHaveBeenCalled();
expect(screen.getByLabelText('Name')).toHaveValue('John');
});
it('shows error message when isInvalid', () => {
render(<Input label="Email" isInvalid errorMessage="Invalid email" />);
expect(screen.getByRole('textbox')).toHaveAttribute('aria-invalid', 'true');
expect(screen.getByText('Invalid email')).toBeInTheDocument();
});
it('is associated with label via htmlFor', () => {
render(<Input label="Username" />);
const label = screen.getByText('Username');
const input = screen.getByLabelText('Username');
expect(label).toBeInTheDocument();
expect(input).toBeInTheDocument();
});
});
```
---
## Storybook Interaction Tests
Use Storybook's `play` function for higher-level interaction scenarios. These run in browser context and can be executed in CI via Storybook Test Runner.
```tsx
// Select.stories.tsx
import { within, userEvent, expect, fn } from '@storybook/test';
export const SelectAnOption: Story = {
args: {
options: [
{ label: 'Option A', value: 'a' },
{ label: 'Option B', value: 'b' },
],
onChange: fn(),
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
// Open dropdown
await userEvent.click(canvas.getByRole('combobox'));
// Select an option
await userEvent.click(canvas.getByRole('option', { name: 'Option B' }));
// Verify onChange called with correct value
await expect(args.onChange).toHaveBeenCalledWith('b');
// Verify display updated
await expect(canvas.getByRole('combobox')).toHaveTextContent('Option B');
},
};
```
**Run Storybook interaction tests in CI:**
```bash
pnpm add -D @storybook/test-runner
# package.json
"test:storybook": "test-storybook --url http://localhost:6006"
```
---
## Visual Regression with Chromatic
Chromatic captures screenshots of every story and detects pixel-level regressions.
### Setup
```bash
pnpm add -D chromatic
```
```bash
# First run — establishes baselines
npx chromatic --project-token=<TOKEN> --auto-accept-changes
# Subsequent runs — diffs against baseline
npx chromatic --project-token=<TOKEN>
```
### What Chromatic Tests
- Every story = one screenshot per viewport
- Diffs shown in Chromatic UI
- Reviewers approve or reject changes
- PRs blocked until visual changes are approved
### Chromatic Configuration
```js
// chromatic.config.js
module.exports = {
// Skip stories with these tags from visual testing
// (useful for very complex animated stories)
disableSnapshot: false,
// Delay before screenshot (for animations to settle)
delay: 200,
// Viewports to capture
viewports: [375, 768, 1440],
};
```
### Story-level overrides
```tsx
export const AnimatedCard: Story = {
parameters: {
chromatic: { delay: 500, viewports: [768, 1440] },
},
};
// Skip visual regression for this story
export const LoadingSpinner: Story = {
parameters: {
chromatic: { disableSnapshot: true },
},
};
```
---
## Accessibility Testing
### Automated (jest-axe)
```tsx
it('has no a11y violations', async () => {
const { container } = render(
<Select
label="Country"
options={[{ label: 'US', value: 'us' }]}
value="us"
onChange={() => {}}
/>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
```
### Storybook a11y Addon
- Automatically runs axe-core on every story in dev
- Shows violations in the Accessibility panel
- Fails CI when configured:
```ts
// .storybook/test-runner.ts
import { checkA11y } from 'axe-playwright';
module.exports = {
async postVisit(page) {
await checkA11y(page, '#storybook-root', {
detailedReport: true,
detailedReportOptions: { html: true },
});
},
};
```
### Manual Checklist
- [ ] All interactive elements reachable by keyboard (Tab, Shift+Tab)
- [ ] Enter/Space activate buttons; Enter follows links
- [ ] Modals trap focus
- [ ] `aria-live` regions announce dynamic content
- [ ] Test with screen reader: VoiceOver (macOS), NVDA (Windows)
- [ ] Zoom to 200% — layout must not break
---
## Coverage Requirements
| Metric | Threshold |
|---|---|
| Line coverage | ≥ 80% |
| Function coverage | ≥ 80% |
| Branch coverage | ≥ 70% |
| New components | 100% smoke tests |
Configure in `vitest.config.ts`:
```ts
coverage: {
thresholds: { lines: 80, functions: 80, branches: 70 }
}
```
---
## CI Pipeline Integration
```yaml
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- run: pnpm install
- run: pnpm build
- run: pnpm test --coverage
- run: pnpm test:storybook
env:
STORYBOOK_URL: http://localhost:6006
```
FILE:references/theming.md
# Theming Reference
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [CSS Custom Properties Strategy](#css-custom-properties-strategy)
3. [Dark Mode Implementation](#dark-mode-implementation)
4. [React ThemeProvider](#react-themeprovider)
5. [Vue 3 Theme System](#vue-3-theme-system)
6. [Runtime Theme Switching](#runtime-theme-switching)
7. [Tailwind CSS Integration](#tailwind-css-integration)
---
## Architecture Overview
The theming system has three layers:
```
Layer 1: Primitive Tokens → Raw values (hex colors, px sizes)
Layer 2: Semantic Tokens → Intent-based CSS variables (light/dark variants)
Layer 3: Component Tokens → Component-scoped overrides (optional)
```
**The rule:** Components only reference **semantic tokens**. Switching a theme only requires swapping the semantic layer — components need zero changes.
```
Light theme: --ds-color-surface = #ffffff
Dark theme: --ds-color-surface = #0f172a
<Card> always uses: background: var(--ds-color-surface)
```
---
## CSS Custom Properties Strategy
### Primitive + Semantic Separation
```css
/* packages/tokens/dist/tokens.css */
/* ─── Primitives (global, never change) ─── */
:root {
--ds-blue-50: #eff6ff;
--ds-blue-500: #3b82f6;
--ds-blue-600: #2563eb;
--ds-blue-700: #1d4ed8;
--ds-gray-50: #f9fafb;
--ds-gray-100: #f3f4f6;
--ds-gray-900: #111827;
--ds-white: #ffffff;
--ds-black: #000000;
}
/* ─── Light Theme (default) ─── */
:root,
[data-theme="light"] {
/* Surfaces */
--ds-color-bg-base: var(--ds-white);
--ds-color-bg-subtle: var(--ds-gray-50);
--ds-color-bg-muted: var(--ds-gray-100);
/* Text */
--ds-color-text-primary: var(--ds-gray-900);
--ds-color-text-secondary: #4b5563; /* gray-600 */
--ds-color-text-disabled: #9ca3af; /* gray-400 */
--ds-color-text-inverse: var(--ds-white);
/* Interactive */
--ds-color-primary: var(--ds-blue-600);
--ds-color-primary-hover: var(--ds-blue-700);
--ds-color-primary-active: var(--ds-blue-700);
--ds-color-primary-subtle: var(--ds-blue-50);
--ds-color-primary-text: var(--ds-white);
/* Borders */
--ds-color-border-default: #e5e7eb; /* gray-200 */
--ds-color-border-strong: #9ca3af; /* gray-400 */
/* Feedback */
--ds-color-error: #dc2626;
--ds-color-warning: #d97706;
--ds-color-success: #16a34a;
--ds-color-info: var(--ds-blue-600);
/* Shadows */
--ds-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1);
--ds-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
--ds-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
/* ─── Dark Theme ─── */
[data-theme="dark"] {
/* Surfaces */
--ds-color-bg-base: #0f172a; /* slate-900 */
--ds-color-bg-subtle: #1e293b; /* slate-800 */
--ds-color-bg-muted: #334155; /* slate-700 */
/* Text */
--ds-color-text-primary: #f1f5f9; /* slate-100 */
--ds-color-text-secondary: #94a3b8; /* slate-400 */
--ds-color-text-disabled: #475569; /* slate-600 */
--ds-color-text-inverse: #0f172a;
/* Interactive */
--ds-color-primary: var(--ds-blue-500); /* lighter in dark mode */
--ds-color-primary-hover: var(--ds-blue-600);
--ds-color-primary-active: var(--ds-blue-700);
--ds-color-primary-subtle: #1e3a8a; /* blue-900 */
--ds-color-primary-text: var(--ds-white);
/* Borders */
--ds-color-border-default: #334155; /* slate-700 */
--ds-color-border-strong: #475569; /* slate-600 */
/* Shadows (stronger in dark mode) */
--ds-shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.4);
--ds-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4);
--ds-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4);
}
```
---
## Dark Mode Implementation
### Strategy Options
| Strategy | How it works | Pros | Cons |
|---|---|---|---|
| **Media query** | `@media (prefers-color-scheme: dark)` | Auto, no JS | Can't override user choice |
| **Data attribute** | `[data-theme="dark"]` on `<html>` | Full control, user preference | Requires JS |
| **Class-based** | `.dark` on `<html>` (Tailwind style) | Familiar, works with Tailwind | Coupling to class names |
**Recommended: data attribute** — gives user control while respecting system preference.
### Implementation
```ts
// packages/utils/src/theme.ts
type Theme = 'light' | 'dark' | 'system';
const STORAGE_KEY = 'ds-theme';
function getSystemTheme(): 'light' | 'dark' {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
function applyTheme(theme: 'light' | 'dark') {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
}
export function initTheme() {
const saved = localStorage.getItem(STORAGE_KEY) as Theme | null;
const resolved = saved === 'dark' || saved === 'light'
? saved
: getSystemTheme();
applyTheme(resolved);
return resolved;
}
export function setTheme(theme: Theme) {
localStorage.setItem(STORAGE_KEY, theme);
const resolved = theme === 'system' ? getSystemTheme() : theme;
applyTheme(resolved);
}
// Watch for system preference changes
export function watchSystemTheme(onChange: (theme: 'light' | 'dark') => void) {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
mq.addEventListener('change', (e) => onChange(e.matches ? 'dark' : 'light'));
}
```
---
## React ThemeProvider
```tsx
// packages/themes/src/ThemeProvider.tsx
import * as React from 'react';
import { initTheme, setTheme, watchSystemTheme } from '@myds/utils/theme';
type Theme = 'light' | 'dark' | 'system';
interface ThemeContextValue {
theme: Theme;
resolvedTheme: 'light' | 'dark';
setTheme: (theme: Theme) => void;
}
const ThemeContext = React.createContext<ThemeContextValue | null>(null);
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = React.useState<Theme>('system');
const [resolvedTheme, setResolvedTheme] = React.useState<'light' | 'dark'>('light');
React.useEffect(() => {
const resolved = initTheme();
setResolvedTheme(resolved);
watchSystemTheme((systemTheme) => {
const saved = localStorage.getItem('ds-theme') as Theme;
if (!saved || saved === 'system') {
setResolvedTheme(systemTheme);
}
});
}, []);
const handleSetTheme = React.useCallback((newTheme: Theme) => {
setThemeState(newTheme);
setTheme(newTheme);
setResolvedTheme(newTheme === 'system'
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: newTheme
);
}, []);
return (
<ThemeContext.Provider value={{ theme, resolvedTheme, setTheme: handleSetTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = React.useContext(ThemeContext);
if (!ctx) throw new Error('useTheme must be used within ThemeProvider');
return ctx;
}
```
### Theme Toggle Component
```tsx
export function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme();
return (
<button
onClick={() => setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')}
aria-label={`Switch to 'dark' mode`}
>
{resolvedTheme === 'dark' ? '☀️' : '🌙'}
</button>
);
}
```
---
## Vue 3 Theme System
```ts
// composables/useTheme.ts
import { ref, watchEffect } from 'vue';
type Theme = 'light' | 'dark' | 'system';
const theme = ref<Theme>('system');
const resolvedTheme = ref<'light' | 'dark'>('light');
function getSystemTheme(): 'light' | 'dark' {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
watchEffect(() => {
const resolved = theme.value === 'system' ? getSystemTheme() : theme.value;
resolvedTheme.value = resolved;
document.documentElement.setAttribute('data-theme', resolved);
document.documentElement.style.colorScheme = resolved;
});
export function useTheme() {
return { theme, resolvedTheme };
}
```
**Provide at app root:**
```ts
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// Initialize theme before mount
const saved = localStorage.getItem('ds-theme');
document.documentElement.setAttribute(
'data-theme',
saved || (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
);
app.mount('#app');
```
---
## Runtime Theme Switching
### No Flash of Wrong Theme (FWOT)
Add to `<head>` before any JS bundles:
```html
<script>
(function() {
var theme = localStorage.getItem('ds-theme');
var dark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var resolved = theme === 'dark' || (!theme && dark) ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', resolved);
document.documentElement.style.colorScheme = resolved;
})();
</script>
```
This runs synchronously, preventing the flash before React/Vue hydrates.
---
## Tailwind CSS Integration
If using Tailwind, configure it to use your CSS variables:
```js
// tailwind.config.js
module.exports = {
darkMode: ['selector', '[data-theme="dark"]'],
theme: {
extend: {
colors: {
primary: {
DEFAULT: 'var(--ds-color-primary)',
hover: 'var(--ds-color-primary-hover)',
subtle: 'var(--ds-color-primary-subtle)',
},
bg: {
base: 'var(--ds-color-bg-base)',
subtle: 'var(--ds-color-bg-subtle)',
},
text: {
primary: 'var(--ds-color-text-primary)',
secondary: 'var(--ds-color-text-secondary)',
},
border: {
default: 'var(--ds-color-border-default)',
},
},
},
},
};
```
FILE:references/tokens.md
# Design Tokens Reference
## Table of Contents
1. [Token Philosophy](#token-philosophy)
2. [Token Categories & Schema](#token-categories--schema)
3. [Naming Conventions](#naming-conventions)
4. [Style Dictionary Setup](#style-dictionary-setup)
5. [CSS Custom Properties Output](#css-custom-properties-output)
6. [JS/TS Constants Output](#jsts-constants-output)
7. [Multi-Brand Tokens](#multi-brand-tokens)
---
## Token Philosophy
Tokens exist in **two layers**:
| Layer | Purpose | Examples |
|---|---|---|
| **Primitive** | Raw values, full palette | `color.blue.500 = #3b82f6` |
| **Semantic** | Intent-based aliases | `color.primary = {color.blue.500}` |
**Rule:** Components always consume **semantic tokens**. Never reference primitive tokens directly in component code.
```
Design Decision → Primitive Token → Semantic Token → Component
"Brand blue" blue-500 primary <Button>
```
---
## Token Categories & Schema
### Color Tokens
**Primitives** (`packages/tokens/src/primitives/color.json`):
```json
{
"color": {
"blue": {
"50": { "value": "#eff6ff" },
"100": { "value": "#dbeafe" },
"200": { "value": "#bfdbfe" },
"300": { "value": "#93c5fd" },
"400": { "value": "#60a5fa" },
"500": { "value": "#3b82f6" },
"600": { "value": "#2563eb" },
"700": { "value": "#1d4ed8" },
"800": { "value": "#1e40af" },
"900": { "value": "#1e3a8a" }
},
"gray": { "...": "50–950 steps" },
"red": { "...": "50–900 steps" },
"green": { "...": "50–900 steps" }
}
}
```
**Semantic** (`packages/tokens/src/semantic/color.json`):
```json
{
"color": {
"primary": {
"default": { "value": "{color.blue.600}" },
"hover": { "value": "{color.blue.700}" },
"active": { "value": "{color.blue.800}" },
"disabled": { "value": "{color.blue.300}" },
"subtle": { "value": "{color.blue.50}" }
},
"neutral": {
"text": {
"primary": { "value": "{color.gray.900}" },
"secondary": { "value": "{color.gray.600}" },
"disabled": { "value": "{color.gray.400}" },
"inverse": { "value": "{color.gray.50}" }
},
"bg": {
"base": { "value": "{color.gray.50}" },
"subtle": { "value": "{color.gray.100}" },
"muted": { "value": "{color.gray.200}" }
},
"border": {
"default": { "value": "{color.gray.200}" },
"strong": { "value": "{color.gray.400}" }
}
},
"feedback": {
"error": { "default": { "value": "{color.red.600}" }, "subtle": { "value": "{color.red.50}" } },
"warning": { "default": { "value": "{color.yellow.500}" }, "subtle": { "value": "{color.yellow.50}" } },
"success": { "default": { "value": "{color.green.600}" }, "subtle": { "value": "{color.green.50}" } },
"info": { "default": { "value": "{color.blue.600}" }, "subtle": { "value": "{color.blue.50}" } }
}
}
}
```
### Typography Tokens
```json
{
"font": {
"family": {
"sans": { "value": "'Inter', system-ui, -apple-system, sans-serif" },
"mono": { "value": "'JetBrains Mono', 'Fira Code', monospace" }
},
"size": {
"xs": { "value": "0.75rem" },
"sm": { "value": "0.875rem" },
"md": { "value": "1rem" },
"lg": { "value": "1.125rem" },
"xl": { "value": "1.25rem" },
"2xl": { "value": "1.5rem" },
"3xl": { "value": "1.875rem" },
"4xl": { "value": "2.25rem" }
},
"weight": {
"regular": { "value": "400" },
"medium": { "value": "500" },
"semibold": { "value": "600" },
"bold": { "value": "700" }
},
"lineHeight": {
"tight": { "value": "1.25" },
"snug": { "value": "1.375" },
"normal": { "value": "1.5" },
"relaxed": { "value": "1.625" }
},
"letterSpacing": {
"tight": { "value": "-0.025em" },
"normal": { "value": "0em" },
"wide": { "value": "0.025em" }
}
}
}
```
### Spacing Tokens
Use a **4px base** grid:
```json
{
"spacing": {
"0": { "value": "0" },
"1": { "value": "0.25rem" },
"2": { "value": "0.5rem" },
"3": { "value": "0.75rem" },
"4": { "value": "1rem" },
"5": { "value": "1.25rem" },
"6": { "value": "1.5rem" },
"8": { "value": "2rem" },
"10": { "value": "2.5rem" },
"12": { "value": "3rem" },
"16": { "value": "4rem" },
"20": { "value": "5rem" },
"24": { "value": "6rem" }
}
}
```
### Border Radius Tokens
```json
{
"borderRadius": {
"none": { "value": "0" },
"sm": { "value": "0.125rem" },
"md": { "value": "0.375rem" },
"lg": { "value": "0.5rem" },
"xl": { "value": "0.75rem" },
"2xl": { "value": "1rem" },
"full": { "value": "9999px" }
}
}
```
### Shadow Tokens
```json
{
"shadow": {
"xs": { "value": "0 1px 2px 0 rgb(0 0 0 / 0.05)" },
"sm": { "value": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)" },
"md": { "value": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)" },
"lg": { "value": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)" },
"xl": { "value": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)" }
}
}
```
### Motion Tokens
```json
{
"motion": {
"duration": {
"fast": { "value": "100ms" },
"normal": { "value": "200ms" },
"slow": { "value": "300ms" },
"slower": { "value": "500ms" }
},
"easing": {
"default": { "value": "cubic-bezier(0.4, 0, 0.2, 1)" },
"in": { "value": "cubic-bezier(0.4, 0, 1, 1)" },
"out": { "value": "cubic-bezier(0, 0, 0.2, 1)" },
"spring": { "value": "cubic-bezier(0.34, 1.56, 0.64, 1)" }
}
}
}
```
### Z-Index Tokens
```json
{
"zIndex": {
"hide": { "value": "-1" },
"base": { "value": "0" },
"raised": { "value": "1" },
"dropdown": { "value": "1000" },
"sticky": { "value": "1100" },
"overlay": { "value": "1200" },
"modal": { "value": "1300" },
"popover": { "value": "1400" },
"toast": { "value": "1500" },
"tooltip": { "value": "1600" }
}
}
```
---
## Naming Conventions
Pattern: `{category}.{variant}.{state}` or `{category}.{scale}`
| Good | Bad |
|---|---|
| `color.primary.default` | `primaryColor` |
| `color.neutral.text.secondary` | `grayText` |
| `spacing.4` | `spaceMedium` |
| `shadow.md` | `boxShadowCard` |
**Key rules:**
- Lowercase, dot-separated for JSON; kebab-case for CSS vars
- Semantic names over visual names (`color.primary` not `color.blue`)
- Scale tokens use numeric or T-shirt sizing (`sm`, `md`, `lg`)
- State suffixes: `default`, `hover`, `active`, `disabled`, `focus`
---
## Style Dictionary Setup
**`packages/tokens/style-dictionary.config.js`:**
```js
const StyleDictionary = require('style-dictionary');
module.exports = {
source: ['src/**/*.json'],
platforms: {
css: {
transformGroup: 'css',
prefix: 'ds',
buildPath: 'dist/',
files: [{
destination: 'tokens.css',
format: 'css/variables',
options: { outputReferences: true }
}]
},
js: {
transformGroup: 'js',
buildPath: 'dist/',
files: [{
destination: 'tokens.js',
format: 'javascript/es6'
}]
},
ts: {
transformGroup: 'js',
buildPath: 'dist/',
files: [{
destination: 'tokens.d.ts',
format: 'typescript/es6-declarations'
}]
}
}
};
```
Build: `npx style-dictionary build --config style-dictionary.config.js`
---
## CSS Custom Properties Output
Generated `dist/tokens.css`:
```css
:root {
/* Primitives */
--ds-color-blue-500: #3b82f6;
--ds-color-gray-900: #111827;
/* Semantics */
--ds-color-primary-default: var(--ds-color-blue-600);
--ds-color-neutral-text-primary: var(--ds-color-gray-900);
/* Spacing */
--ds-spacing-4: 1rem;
--ds-spacing-8: 2rem;
/* Typography */
--ds-font-size-md: 1rem;
--ds-font-weight-semibold: 600;
}
```
---
## JS/TS Constants Output
```ts
// dist/tokens.js
export const ColorBlue500 = "#3b82f6";
export const ColorPrimaryDefault = "#2563eb";
export const Spacing4 = "1rem";
export const FontSizeMd = "1rem";
```
---
## Multi-Brand Tokens
For multi-brand scenarios, create brand-specific semantic overrides:
```
packages/tokens/src/
├── primitives/ # Shared raw values
│ ├── color.json
│ └── spacing.json
└── brands/
├── brand-a/ # Brand A semantic mapping
│ └── semantic.json
└── brand-b/ # Brand B semantic mapping
└── semantic.json
```
Build separate CSS files per brand, then load the correct one at runtime:
```js
// Load brand at app entry point
import(`@myds/tokens/dist/brand/tokens.css`);
```