mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: add issue discovery view for managing discovery sessions and findings
- Implemented main render function for the issue discovery view. - Added data loading functions to fetch discoveries, details, findings, and progress. - Created rendering functions for discovery list and detail sections. - Introduced filtering and searching capabilities for findings. - Implemented actions for exporting and dismissing findings. - Added polling mechanism to track discovery progress. - Included utility functions for HTML escaping and cleanup.
This commit is contained in:
177
.claude/skills/software-manual/SKILL.md
Normal file
177
.claude/skills/software-manual/SKILL.md
Normal file
@@ -0,0 +1,177 @@
|
||||
---
|
||||
name: software-manual
|
||||
description: Generate interactive TiddlyWiki-style HTML software manuals with screenshots, API docs, and multi-level code examples. Use when creating user guides, software documentation, or API references. Triggers on "software manual", "user guide", "generate manual", "create docs".
|
||||
allowed-tools: Task, AskUserQuestion, Read, Bash, Glob, Grep, Write, mcp__chrome__*
|
||||
---
|
||||
|
||||
# Software Manual Skill
|
||||
|
||||
Generate comprehensive, interactive software manuals in TiddlyWiki-style single-file HTML format.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Context-Optimized Architecture │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Phase 1: Requirements → manual-config.json │
|
||||
│ ↓ │
|
||||
│ Phase 2: Exploration → exploration-*.json │
|
||||
│ ↓ │
|
||||
│ Phase 3: Parallel Agents → sections/section-*.md │
|
||||
│ ↓ (6 Agents) │
|
||||
│ Phase 3.5: Consolidation → consolidation-summary.md │
|
||||
│ ↓ │
|
||||
│ Phase 4: Screenshot → screenshots/*.png │
|
||||
│ Capture (via Chrome MCP) │
|
||||
│ ↓ │
|
||||
│ Phase 5: HTML Assembly → {name}-使用手册.html │
|
||||
│ ↓ │
|
||||
│ Phase 6: Refinement → iterations/ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
1. **主 Agent 编排,子 Agent 执行**: 所有繁重计算委托给 `universal-executor` 子 Agent
|
||||
2. **Brief Returns**: Agents return path + summary, not full content (avoid context overflow)
|
||||
3. **System Agents**: 使用 `cli-explore-agent` (探索) 和 `universal-executor` (执行)
|
||||
4. **Chrome MCP Integration**: Batch screenshot capture with Base64 embedding
|
||||
5. **Single-File HTML**: TiddlyWiki-style interactive document with embedded resources
|
||||
6. **User-Friendly Writing**: Clear, step-by-step guides with difficulty levels
|
||||
|
||||
## Execution Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Phase 1: Requirements Discovery (主 Agent) │
|
||||
│ → AskUserQuestion: 收集软件类型、目标用户、文档范围 │
|
||||
│ → Output: manual-config.json │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Phase 2: Project Exploration (cli-explore-agent × N) │
|
||||
│ → 并行探索: architecture, ui-routes, api-endpoints, config │
|
||||
│ → Output: exploration-*.json │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Phase 3: Parallel Analysis (universal-executor × 6) │
|
||||
│ → 6 个子 Agent 并行: overview, ui-guide, api-docs, config, │
|
||||
│ troubleshooting, code-examples │
|
||||
│ → Output: sections/section-*.md │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Phase 3.5: Consolidation (universal-executor) │
|
||||
│ → 质量检查: 一致性、交叉引用、截图标记 │
|
||||
│ → Output: consolidation-summary.md, screenshots-list.json │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Phase 4: Screenshot Capture (主 Agent + Chrome MCP) │
|
||||
│ → 批量截图: 根据 screenshots-list.json │
|
||||
│ → Output: screenshots/*.png + manifest.json │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Phase 5: HTML Assembly (universal-executor) │
|
||||
│ → 组装 HTML: MD→tiddlers, 嵌入 CSS/JS/图片 │
|
||||
│ → Output: {name}-使用手册.html │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Phase 6: Iterative Refinement (主 Agent) │
|
||||
│ → 预览 + 用户反馈 + 迭代修复 │
|
||||
│ → Output: iterations/v*.html │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Agent Configuration
|
||||
|
||||
| Agent | Role | Output File | Focus Areas |
|
||||
|-------|------|-------------|-------------|
|
||||
| overview | Product Manager | section-overview.md | Product intro, features, quick start |
|
||||
| ui-guide | UX Expert | section-ui-guide.md | UI operations, step-by-step guides |
|
||||
| api-docs | API Architect | section-api-reference.md | REST API, Frontend API |
|
||||
| config | DevOps Engineer | section-configuration.md | Env vars, deployment, settings |
|
||||
| troubleshooting | Support Engineer | section-troubleshooting.md | FAQs, error codes, solutions |
|
||||
| code-examples | Developer Advocate | section-examples.md | Beginner/Intermediate/Advanced examples |
|
||||
|
||||
## Agent Return Format
|
||||
|
||||
```typescript
|
||||
interface ManualAgentReturn {
|
||||
status: "completed" | "partial" | "failed";
|
||||
output_file: string;
|
||||
summary: string; // Max 50 chars
|
||||
screenshots_needed: Array<{
|
||||
id: string; // e.g., "ss-login-form"
|
||||
url: string; // Relative or absolute URL
|
||||
description: string; // "Login form interface"
|
||||
selector?: string; // CSS selector for partial screenshot
|
||||
wait_for?: string; // Element to wait for
|
||||
}>;
|
||||
cross_references: string[]; // Other sections referenced
|
||||
difficulty_level: "beginner" | "intermediate" | "advanced";
|
||||
}
|
||||
```
|
||||
|
||||
## HTML Features (TiddlyWiki-style)
|
||||
|
||||
1. **Search**: Full-text search with result highlighting
|
||||
2. **Collapse/Expand**: Per-section collapsible content
|
||||
3. **Tag Navigation**: Filter by category tags
|
||||
4. **Theme Toggle**: Light/Dark mode with localStorage persistence
|
||||
5. **Single File**: All CSS/JS/images embedded as Base64
|
||||
6. **Offline**: Works without internet connection
|
||||
7. **Print-friendly**: Optimized print stylesheet
|
||||
|
||||
## Directory Setup
|
||||
|
||||
```javascript
|
||||
// Generate timestamp directory name
|
||||
const timestamp = new Date().toISOString().slice(0,19).replace(/[-:T]/g, '');
|
||||
const dir = `.workflow/.scratchpad/manual-${timestamp}`;
|
||||
|
||||
// Windows
|
||||
Bash(`mkdir "${dir}\\sections" && mkdir "${dir}\\screenshots" && mkdir "${dir}\\api-docs" && mkdir "${dir}\\iterations"`);
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
.workflow/.scratchpad/manual-{timestamp}/
|
||||
├── manual-config.json # Phase 1
|
||||
├── exploration/ # Phase 2
|
||||
│ ├── exploration-architecture.json
|
||||
│ ├── exploration-ui-routes.json
|
||||
│ └── exploration-api-endpoints.json
|
||||
├── sections/ # Phase 3
|
||||
│ ├── section-overview.md
|
||||
│ ├── section-ui-guide.md
|
||||
│ ├── section-api-reference.md
|
||||
│ ├── section-configuration.md
|
||||
│ ├── section-troubleshooting.md
|
||||
│ └── section-examples.md
|
||||
├── consolidation-summary.md # Phase 3.5
|
||||
├── api-docs/ # API documentation
|
||||
│ ├── frontend/ # TypeDoc output
|
||||
│ └── backend/ # Swagger/OpenAPI output
|
||||
├── screenshots/ # Phase 4
|
||||
│ ├── ss-*.png
|
||||
│ └── screenshots-manifest.json
|
||||
├── iterations/ # Phase 6
|
||||
│ ├── v1.html
|
||||
│ └── v2.html
|
||||
└── {软件名}-使用手册.html # Final Output
|
||||
```
|
||||
|
||||
## Reference Documents
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [phases/01-requirements-discovery.md](phases/01-requirements-discovery.md) | User config collection |
|
||||
| [phases/02-project-exploration.md](phases/02-project-exploration.md) | Project type detection |
|
||||
| [phases/03-parallel-analysis.md](phases/03-parallel-analysis.md) | 6 Agent orchestration |
|
||||
| [phases/03.5-consolidation.md](phases/03.5-consolidation.md) | Cross-section synthesis |
|
||||
| [phases/04-screenshot-capture.md](phases/04-screenshot-capture.md) | Chrome MCP integration |
|
||||
| [phases/05-html-assembly.md](phases/05-html-assembly.md) | HTML generation |
|
||||
| [phases/06-iterative-refinement.md](phases/06-iterative-refinement.md) | Quality iteration |
|
||||
| [specs/quality-standards.md](specs/quality-standards.md) | Quality gates |
|
||||
| [specs/writing-style.md](specs/writing-style.md) | User-friendly writing |
|
||||
| [specs/html-template.md](specs/html-template.md) | HTML template spec |
|
||||
| [templates/tiddlywiki-shell.html](templates/tiddlywiki-shell.html) | HTML template |
|
||||
| [scripts/typedoc-runner.md](scripts/typedoc-runner.md) | TypeDoc execution |
|
||||
| [scripts/swagger-runner.md](scripts/swagger-runner.md) | Swagger/OpenAPI |
|
||||
| [scripts/screenshot-helper.md](scripts/screenshot-helper.md) | Chrome MCP guide |
|
||||
@@ -0,0 +1,162 @@
|
||||
# Phase 1: Requirements Discovery
|
||||
|
||||
Collect user requirements and generate configuration for the manual generation process.
|
||||
|
||||
## Objective
|
||||
|
||||
Gather essential information about the software project to customize the manual generation:
|
||||
- Software type and characteristics
|
||||
- Target user audience
|
||||
- Documentation scope and depth
|
||||
- Special requirements
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Software Information Collection
|
||||
|
||||
Use `AskUserQuestion` to collect:
|
||||
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "What type of software is this project?",
|
||||
header: "Software Type",
|
||||
options: [
|
||||
{ label: "Web Application", description: "Frontend + Backend web app with UI" },
|
||||
{ label: "CLI Tool", description: "Command-line interface tool" },
|
||||
{ label: "SDK/Library", description: "Developer library or SDK" },
|
||||
{ label: "Desktop App", description: "Desktop application (Electron, etc.)" }
|
||||
],
|
||||
multiSelect: false
|
||||
},
|
||||
{
|
||||
question: "Who is the target audience for this manual?",
|
||||
header: "Target Users",
|
||||
options: [
|
||||
{ label: "End Users", description: "Non-technical users who use the product" },
|
||||
{ label: "Developers", description: "Developers integrating or extending the product" },
|
||||
{ label: "Administrators", description: "System admins deploying and maintaining" },
|
||||
{ label: "All Audiences", description: "Mixed audience with different sections" }
|
||||
],
|
||||
multiSelect: false
|
||||
},
|
||||
{
|
||||
question: "What documentation scope do you need?",
|
||||
header: "Doc Scope",
|
||||
options: [
|
||||
{ label: "Quick Start", description: "Essential getting started guide only" },
|
||||
{ label: "User Guide", description: "Complete user-facing documentation" },
|
||||
{ label: "API Reference", description: "Focus on API documentation" },
|
||||
{ label: "Comprehensive", description: "Full documentation including all sections" }
|
||||
],
|
||||
multiSelect: false
|
||||
},
|
||||
{
|
||||
question: "What difficulty levels should code examples cover?",
|
||||
header: "Example Levels",
|
||||
options: [
|
||||
{ label: "Beginner Only", description: "Simple, basic examples" },
|
||||
{ label: "Beginner + Intermediate", description: "Basic to moderate complexity" },
|
||||
{ label: "All Levels", description: "Beginner, Intermediate, and Advanced" }
|
||||
],
|
||||
multiSelect: false
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### Step 2: Auto-Detection (Supplement)
|
||||
|
||||
Automatically detect project characteristics:
|
||||
|
||||
```javascript
|
||||
// Detect from package.json
|
||||
const packageJson = Read('package.json');
|
||||
const softwareName = packageJson.name;
|
||||
const version = packageJson.version;
|
||||
const description = packageJson.description;
|
||||
|
||||
// Detect tech stack
|
||||
const hasReact = packageJson.dependencies?.react;
|
||||
const hasVue = packageJson.dependencies?.vue;
|
||||
const hasExpress = packageJson.dependencies?.express;
|
||||
const hasNestJS = packageJson.dependencies?.['@nestjs/core'];
|
||||
|
||||
// Detect CLI
|
||||
const hasBin = !!packageJson.bin;
|
||||
|
||||
// Detect UI
|
||||
const hasPages = Glob('src/pages/**/*').length > 0 || Glob('pages/**/*').length > 0;
|
||||
const hasRoutes = Glob('**/routes.*').length > 0;
|
||||
```
|
||||
|
||||
### Step 3: Generate Configuration
|
||||
|
||||
Create `manual-config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"software": {
|
||||
"name": "{{detected or user input}}",
|
||||
"version": "{{from package.json}}",
|
||||
"description": "{{from package.json}}",
|
||||
"type": "{{web|cli|sdk|desktop}}"
|
||||
},
|
||||
"target_audience": "{{end_users|developers|admins|all}}",
|
||||
"doc_scope": "{{quick_start|user_guide|api_reference|comprehensive}}",
|
||||
"example_levels": ["beginner", "intermediate", "advanced"],
|
||||
"tech_stack": {
|
||||
"frontend": "{{react|vue|angular|vanilla}}",
|
||||
"backend": "{{express|nestjs|fastify|none}}",
|
||||
"language": "{{typescript|javascript}}",
|
||||
"ui_framework": "{{tailwind|mui|antd|none}}"
|
||||
},
|
||||
"features": {
|
||||
"has_ui": true,
|
||||
"has_api": true,
|
||||
"has_cli": false,
|
||||
"has_config": true
|
||||
},
|
||||
"agents_to_run": [
|
||||
"overview",
|
||||
"ui-guide",
|
||||
"api-docs",
|
||||
"config",
|
||||
"troubleshooting",
|
||||
"code-examples"
|
||||
],
|
||||
"screenshot_config": {
|
||||
"enabled": true,
|
||||
"dev_command": "npm run dev",
|
||||
"dev_url": "http://localhost:3000",
|
||||
"wait_timeout": 5000
|
||||
},
|
||||
"output": {
|
||||
"filename": "{{name}}-使用手册.html",
|
||||
"theme": "light",
|
||||
"language": "zh-CN"
|
||||
},
|
||||
"timestamp": "{{ISO8601}}"
|
||||
}
|
||||
```
|
||||
|
||||
## Agent Selection Logic
|
||||
|
||||
Based on `doc_scope`, select agents to run:
|
||||
|
||||
| Scope | Agents |
|
||||
|-------|--------|
|
||||
| quick_start | overview |
|
||||
| user_guide | overview, ui-guide, config, troubleshooting |
|
||||
| api_reference | overview, api-docs, code-examples |
|
||||
| comprehensive | ALL 6 agents |
|
||||
|
||||
## Output
|
||||
|
||||
- **File**: `manual-config.json`
|
||||
- **Location**: `.workflow/.scratchpad/manual-{timestamp}/`
|
||||
|
||||
## Next Phase
|
||||
|
||||
Proceed to [Phase 2: Project Exploration](02-project-exploration.md) with the generated configuration.
|
||||
101
.claude/skills/software-manual/phases/02-project-exploration.md
Normal file
101
.claude/skills/software-manual/phases/02-project-exploration.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Phase 2: Project Exploration
|
||||
|
||||
使用 `cli-explore-agent` 探索项目结构,生成文档所需的结构化数据。
|
||||
|
||||
## 探索角度
|
||||
|
||||
```javascript
|
||||
const EXPLORATION_ANGLES = {
|
||||
web: ['architecture', 'ui-routes', 'api-endpoints', 'config'],
|
||||
cli: ['architecture', 'commands', 'config'],
|
||||
sdk: ['architecture', 'public-api', 'types', 'config'],
|
||||
desktop: ['architecture', 'ui-screens', 'config']
|
||||
};
|
||||
```
|
||||
|
||||
## 执行流程
|
||||
|
||||
```javascript
|
||||
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
|
||||
const angles = EXPLORATION_ANGLES[config.software.type];
|
||||
|
||||
// 并行探索
|
||||
const tasks = angles.map(angle => Task({
|
||||
subagent_type: 'cli-explore-agent',
|
||||
run_in_background: false,
|
||||
prompt: buildExplorationPrompt(angle, config, workDir)
|
||||
}));
|
||||
|
||||
const results = await Promise.all(tasks);
|
||||
```
|
||||
|
||||
## 探索配置
|
||||
|
||||
```javascript
|
||||
const EXPLORATION_CONFIGS = {
|
||||
architecture: {
|
||||
task: '分析项目模块结构、入口点、依赖关系',
|
||||
patterns: ['src/*/', 'package.json', 'tsconfig.json'],
|
||||
output: 'exploration-architecture.json'
|
||||
},
|
||||
'ui-routes': {
|
||||
task: '提取 UI 路由、页面组件、导航结构',
|
||||
patterns: ['src/pages/**', 'src/views/**', 'app/**/page.*', 'src/router/**'],
|
||||
output: 'exploration-ui-routes.json'
|
||||
},
|
||||
'api-endpoints': {
|
||||
task: '提取 REST API 端点、请求/响应类型',
|
||||
patterns: ['src/**/*.controller.*', 'src/routes/**', 'openapi.*', 'swagger.*'],
|
||||
output: 'exploration-api-endpoints.json'
|
||||
},
|
||||
config: {
|
||||
task: '提取环境变量、配置文件选项',
|
||||
patterns: ['.env.example', 'config/**', 'docker-compose.yml'],
|
||||
output: 'exploration-config.json'
|
||||
},
|
||||
commands: {
|
||||
task: '提取 CLI 命令、选项、示例',
|
||||
patterns: ['src/cli*', 'bin/*', 'src/commands/**'],
|
||||
output: 'exploration-commands.json'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Prompt 构建
|
||||
|
||||
```javascript
|
||||
function buildExplorationPrompt(angle, config, workDir) {
|
||||
const cfg = EXPLORATION_CONFIGS[angle];
|
||||
return `
|
||||
[TASK]
|
||||
${cfg.task}
|
||||
|
||||
[SCOPE]
|
||||
项目类型: ${config.software.type}
|
||||
扫描模式: deep-scan
|
||||
文件模式: ${cfg.patterns.join(', ')}
|
||||
|
||||
[OUTPUT]
|
||||
文件: ${workDir}/exploration/${cfg.output}
|
||||
格式: JSON (schema-compliant)
|
||||
|
||||
[RETURN]
|
||||
简要说明发现的内容数量和关键发现
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
## 输出结构
|
||||
|
||||
```
|
||||
exploration/
|
||||
├── exploration-architecture.json # 模块结构
|
||||
├── exploration-ui-routes.json # UI 路由
|
||||
├── exploration-api-endpoints.json # API 端点
|
||||
├── exploration-config.json # 配置选项
|
||||
└── exploration-commands.json # CLI 命令 (if CLI)
|
||||
```
|
||||
|
||||
## 下一阶段
|
||||
|
||||
→ [Phase 3: Parallel Analysis](03-parallel-analysis.md)
|
||||
130
.claude/skills/software-manual/phases/03-parallel-analysis.md
Normal file
130
.claude/skills/software-manual/phases/03-parallel-analysis.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Phase 3: Parallel Analysis
|
||||
|
||||
使用 `universal-executor` 并行生成 6 个文档章节。
|
||||
|
||||
## Agent 配置
|
||||
|
||||
```javascript
|
||||
const AGENT_CONFIGS = {
|
||||
overview: {
|
||||
role: 'Product Manager',
|
||||
output: 'section-overview.md',
|
||||
task: '撰写产品概览、核心功能、快速入门指南',
|
||||
focus: '产品定位、目标用户、5步快速入门、系统要求',
|
||||
input: ['exploration-architecture.json', 'README.md', 'package.json']
|
||||
},
|
||||
'ui-guide': {
|
||||
role: 'UX Expert',
|
||||
output: 'section-ui-guide.md',
|
||||
task: '撰写界面操作指南,分步骤说明各功能使用方法',
|
||||
focus: '界面布局、导航流程、功能操作、快捷键',
|
||||
input: ['exploration-ui-routes.json', 'pages/**', 'views/**']
|
||||
},
|
||||
'api-docs': {
|
||||
role: 'API Architect',
|
||||
output: 'section-api-reference.md',
|
||||
task: '撰写 REST API 和前端 API 参考文档',
|
||||
focus: 'API 概览、端点分类、请求/响应示例、错误码',
|
||||
input: ['exploration-api-endpoints.json', 'controllers/**', 'routes/**']
|
||||
},
|
||||
config: {
|
||||
role: 'DevOps Engineer',
|
||||
output: 'section-configuration.md',
|
||||
task: '撰写配置指南,涵盖环境变量、配置文件、部署设置',
|
||||
focus: '环境变量表格、配置文件格式、部署选项、安全设置',
|
||||
input: ['exploration-config.json', '.env.example', 'config/**']
|
||||
},
|
||||
troubleshooting: {
|
||||
role: 'Support Engineer',
|
||||
output: 'section-troubleshooting.md',
|
||||
task: '撰写故障排查指南,涵盖常见问题、错误码、FAQ',
|
||||
focus: '常见问题与解决方案、错误码参考、FAQ、获取帮助',
|
||||
input: ['all exploration files', 'error handling code']
|
||||
},
|
||||
'code-examples': {
|
||||
role: 'Developer Advocate',
|
||||
output: 'section-examples.md',
|
||||
task: '撰写多难度级别代码示例(入门40%/进阶40%/高级20%)',
|
||||
focus: '完整可运行代码、分步解释、预期输出、最佳实践',
|
||||
input: ['all exploration files', 'examples/**', 'tests/**']
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 执行流程
|
||||
|
||||
```javascript
|
||||
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
|
||||
|
||||
// 并行启动 6 个 universal-executor
|
||||
const tasks = Object.entries(AGENT_CONFIGS).map(([name, cfg]) =>
|
||||
Task({
|
||||
subagent_type: 'universal-executor',
|
||||
run_in_background: false,
|
||||
prompt: buildAgentPrompt(name, cfg, config, workDir)
|
||||
})
|
||||
);
|
||||
|
||||
const results = await Promise.all(tasks);
|
||||
```
|
||||
|
||||
## Prompt 构建
|
||||
|
||||
```javascript
|
||||
function buildAgentPrompt(name, cfg, config, workDir) {
|
||||
return `
|
||||
[ROLE] ${cfg.role}
|
||||
|
||||
[TASK]
|
||||
${cfg.task}
|
||||
输出: ${workDir}/sections/${cfg.output}
|
||||
|
||||
[INPUT]
|
||||
- Read: ${workDir}/manual-config.json
|
||||
- Read: ${cfg.input.map(f => `${workDir}/exploration/${f}`).join(', ')}
|
||||
|
||||
[STYLE]
|
||||
- 用户友好语言,避免技术术语
|
||||
- 步骤编号清晰
|
||||
- 代码块标注语言
|
||||
- 截图标记: <!-- SCREENSHOT: id="ss-xxx" url="/path" description="xxx" -->
|
||||
|
||||
[FOCUS]
|
||||
${cfg.focus}
|
||||
|
||||
[RETURN JSON]
|
||||
{
|
||||
"status": "completed",
|
||||
"output_file": "sections/${cfg.output}",
|
||||
"summary": "<50字>",
|
||||
"screenshots_needed": [],
|
||||
"cross_references": []
|
||||
}
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
## 结果收集
|
||||
|
||||
```javascript
|
||||
const agentResults = results.map(r => JSON.parse(r));
|
||||
const allScreenshots = agentResults.flatMap(r => r.screenshots_needed);
|
||||
|
||||
Write(`${workDir}/agent-results.json`, JSON.stringify({
|
||||
results: agentResults,
|
||||
screenshots_needed: allScreenshots,
|
||||
timestamp: new Date().toISOString()
|
||||
}, null, 2));
|
||||
```
|
||||
|
||||
## 质量检查
|
||||
|
||||
- [ ] Markdown 语法有效
|
||||
- [ ] 无占位符文本
|
||||
- [ ] 代码块标注语言
|
||||
- [ ] 截图标记格式正确
|
||||
- [ ] 交叉引用有效
|
||||
|
||||
## 下一阶段
|
||||
|
||||
→ [Phase 3.5: Consolidation](03.5-consolidation.md)
|
||||
82
.claude/skills/software-manual/phases/03.5-consolidation.md
Normal file
82
.claude/skills/software-manual/phases/03.5-consolidation.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Phase 3.5: Consolidation
|
||||
|
||||
使用 `universal-executor` 子 Agent 执行质量检查,避免主 Agent 内存溢出。
|
||||
|
||||
## 核心原则
|
||||
|
||||
**主 Agent 负责编排,子 Agent 负责繁重计算。**
|
||||
|
||||
## 执行流程
|
||||
|
||||
```javascript
|
||||
const agentResults = JSON.parse(Read(`${workDir}/agent-results.json`));
|
||||
|
||||
// 委托给 universal-executor 执行整合检查
|
||||
const result = Task({
|
||||
subagent_type: 'universal-executor',
|
||||
run_in_background: false,
|
||||
prompt: buildConsolidationPrompt(workDir)
|
||||
});
|
||||
|
||||
const consolidationResult = JSON.parse(result);
|
||||
```
|
||||
|
||||
## Prompt 构建
|
||||
|
||||
```javascript
|
||||
function buildConsolidationPrompt(workDir) {
|
||||
return `
|
||||
[ROLE] Quality Analyst
|
||||
|
||||
[TASK]
|
||||
检查所有章节的一致性和完整性
|
||||
|
||||
[INPUT]
|
||||
- 章节文件: ${workDir}/sections/section-*.md
|
||||
- Agent 结果: ${workDir}/agent-results.json
|
||||
|
||||
[CHECKS]
|
||||
1. Markdown 语法有效性
|
||||
2. 截图标记格式 (<!-- SCREENSHOT: id="..." -->)
|
||||
3. 交叉引用有效性
|
||||
4. 术语一致性
|
||||
5. 代码块语言标注
|
||||
|
||||
[OUTPUT]
|
||||
1. 写入 ${workDir}/consolidation-summary.md
|
||||
2. 写入 ${workDir}/screenshots-list.json (截图清单)
|
||||
|
||||
[RETURN JSON]
|
||||
{
|
||||
"status": "completed",
|
||||
"sections_checked": <n>,
|
||||
"screenshots_found": <n>,
|
||||
"issues": { "errors": <n>, "warnings": <n> },
|
||||
"quality_score": <0-100>
|
||||
}
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
## Agent 职责
|
||||
|
||||
1. **读取章节** → 逐个检查 section-*.md
|
||||
2. **提取截图** → 收集所有截图标记
|
||||
3. **验证引用** → 检查交叉引用有效性
|
||||
4. **评估质量** → 计算综合分数
|
||||
5. **输出报告** → consolidation-summary.md
|
||||
|
||||
## 输出
|
||||
|
||||
- `consolidation-summary.md` - 质量报告
|
||||
- `screenshots-list.json` - 截图清单(供 Phase 4 使用)
|
||||
|
||||
## 质量门禁
|
||||
|
||||
- [ ] 无错误
|
||||
- [ ] 总分 >= 60%
|
||||
- [ ] 交叉引用有效
|
||||
|
||||
## 下一阶段
|
||||
|
||||
→ [Phase 4: Screenshot Capture](04-screenshot-capture.md)
|
||||
271
.claude/skills/software-manual/phases/04-screenshot-capture.md
Normal file
271
.claude/skills/software-manual/phases/04-screenshot-capture.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Phase 4: Screenshot Capture
|
||||
|
||||
Capture screenshots using Chrome MCP for all identified UI elements.
|
||||
|
||||
## Objective
|
||||
|
||||
- Check Chrome MCP availability
|
||||
- Start development server
|
||||
- Capture all required screenshots
|
||||
- Convert to Base64 for embedding
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Chrome MCP configured and available
|
||||
- Development server can be started
|
||||
- All screenshot URLs accessible
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Load Screenshot List
|
||||
|
||||
```javascript
|
||||
const consolidation = Read(`${workDir}/consolidation-summary.md`);
|
||||
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
|
||||
|
||||
// Parse screenshot table from consolidation
|
||||
const screenshots = parseScreenshotTable(consolidation);
|
||||
```
|
||||
|
||||
### Step 2: Check Chrome MCP Availability
|
||||
|
||||
```javascript
|
||||
async function checkChromeMCP() {
|
||||
try {
|
||||
// Attempt to call Chrome MCP
|
||||
const version = await mcp__chrome__getVersion();
|
||||
return {
|
||||
available: true,
|
||||
version: version.version,
|
||||
browser: version.browser
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
available: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const chromeMCP = await checkChromeMCP();
|
||||
|
||||
if (!chromeMCP.available) {
|
||||
// Fallback: generate manual screenshot instructions
|
||||
generateManualScreenshotGuide(screenshots);
|
||||
return {
|
||||
status: 'skipped',
|
||||
reason: 'Chrome MCP not available',
|
||||
manual_guide: `${workDir}/screenshots/MANUAL_CAPTURE.md`
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Start Development Server
|
||||
|
||||
```javascript
|
||||
const devConfig = config.screenshot_config;
|
||||
|
||||
// Start dev server in background
|
||||
const serverTask = Bash({
|
||||
command: devConfig.dev_command,
|
||||
run_in_background: true
|
||||
});
|
||||
|
||||
// Wait for server to be ready
|
||||
async function waitForServer(url, timeout = 30000) {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < timeout) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) return true;
|
||||
} catch (e) {
|
||||
// Server not ready yet
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
throw new Error(`Server at ${url} did not start within ${timeout}ms`);
|
||||
}
|
||||
|
||||
await waitForServer(devConfig.dev_url, devConfig.wait_timeout);
|
||||
```
|
||||
|
||||
### Step 4: Batch Screenshot Capture
|
||||
|
||||
```javascript
|
||||
const capturedScreenshots = [];
|
||||
const failedScreenshots = [];
|
||||
|
||||
for (const ss of screenshots) {
|
||||
try {
|
||||
const fullUrl = new URL(ss.url, devConfig.dev_url).href;
|
||||
|
||||
// Configure capture options
|
||||
const captureOptions = {
|
||||
url: fullUrl,
|
||||
viewport: { width: 1280, height: 800 },
|
||||
fullPage: ss.fullPage || false,
|
||||
waitFor: ss.wait_for || null,
|
||||
delay: 500 // Wait for animations
|
||||
};
|
||||
|
||||
// Add selector for partial screenshot
|
||||
if (ss.selector) {
|
||||
captureOptions.selector = ss.selector;
|
||||
}
|
||||
|
||||
// Capture screenshot
|
||||
const result = await mcp__chrome__screenshot(captureOptions);
|
||||
|
||||
// Save screenshot
|
||||
const filename = `${ss.id}.png`;
|
||||
Write(`${workDir}/screenshots/${filename}`, result.data, { encoding: 'base64' });
|
||||
|
||||
capturedScreenshots.push({
|
||||
...ss,
|
||||
file: filename,
|
||||
base64_size: result.data.length,
|
||||
captured_at: new Date().toISOString()
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
failedScreenshots.push({
|
||||
...ss,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Generate Screenshot Manifest
|
||||
|
||||
```javascript
|
||||
const manifest = {
|
||||
total: screenshots.length,
|
||||
captured: capturedScreenshots.length,
|
||||
failed: failedScreenshots.length,
|
||||
screenshots: capturedScreenshots,
|
||||
failures: failedScreenshots,
|
||||
dev_server: {
|
||||
url: devConfig.dev_url,
|
||||
command: devConfig.dev_command
|
||||
},
|
||||
capture_config: {
|
||||
viewport: { width: 1280, height: 800 },
|
||||
format: 'png'
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
Write(`${workDir}/screenshots/screenshots-manifest.json`, JSON.stringify(manifest, null, 2));
|
||||
```
|
||||
|
||||
### Step 6: Stop Development Server
|
||||
|
||||
```javascript
|
||||
// Kill the dev server process
|
||||
KillShell({ shell_id: serverTask.task_id });
|
||||
```
|
||||
|
||||
### Step 7: Handle Failures
|
||||
|
||||
If any screenshots failed:
|
||||
|
||||
```javascript
|
||||
if (failedScreenshots.length > 0) {
|
||||
// Generate manual capture instructions
|
||||
const manualGuide = `
|
||||
# Manual Screenshot Capture Required
|
||||
|
||||
The following screenshots could not be captured automatically:
|
||||
|
||||
${failedScreenshots.map(s => `
|
||||
## ${s.id}
|
||||
- **URL**: ${s.url}
|
||||
- **Description**: ${s.description}
|
||||
- **Error**: ${s.error}
|
||||
|
||||
**Instructions**:
|
||||
1. Navigate to ${s.url}
|
||||
2. Capture screenshot of ${s.selector || 'full page'}
|
||||
3. Save as \`${s.id}.png\`
|
||||
4. Place in \`screenshots/\` directory
|
||||
`).join('\n')}
|
||||
`;
|
||||
|
||||
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, manualGuide);
|
||||
}
|
||||
```
|
||||
|
||||
## Fallback: Manual Screenshot Mode
|
||||
|
||||
When Chrome MCP is not available:
|
||||
|
||||
```javascript
|
||||
function generateManualScreenshotGuide(screenshots) {
|
||||
const guide = `
|
||||
# Manual Screenshot Capture Guide
|
||||
|
||||
Chrome MCP is not available. Please capture screenshots manually.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Start your development server:
|
||||
\`\`\`bash
|
||||
${config.screenshot_config.dev_command}
|
||||
\`\`\`
|
||||
|
||||
2. Open browser to: ${config.screenshot_config.dev_url}
|
||||
|
||||
## Screenshots Required
|
||||
|
||||
${screenshots.map((s, i) => `
|
||||
### ${i + 1}. ${s.id}
|
||||
- **URL**: ${s.url}
|
||||
- **Description**: ${s.description}
|
||||
- **Save as**: \`screenshots/${s.id}.png\`
|
||||
${s.selector ? `- **Element**: Capture only \`${s.selector}\`` : '- **Type**: Full page'}
|
||||
`).join('\n')}
|
||||
|
||||
## After Capturing
|
||||
|
||||
Place all PNG files in the \`screenshots/\` directory, then run Phase 5 to continue.
|
||||
`;
|
||||
|
||||
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, guide);
|
||||
}
|
||||
```
|
||||
|
||||
## Chrome MCP Configuration Reference
|
||||
|
||||
Expected MCP configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"chrome": {
|
||||
"command": "npx",
|
||||
"args": ["@anthropic-ai/mcp-chrome"],
|
||||
"env": {
|
||||
"CHROME_PATH": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
- **Files**: `screenshots/*.png`
|
||||
- **Manifest**: `screenshots/screenshots-manifest.json`
|
||||
- **Fallback**: `screenshots/MANUAL_CAPTURE.md` (if needed)
|
||||
|
||||
## Quality Checks
|
||||
|
||||
- [ ] All high-priority screenshots captured
|
||||
- [ ] Screenshot dimensions consistent (1280x800)
|
||||
- [ ] No broken/blank screenshots
|
||||
- [ ] Manifest file complete
|
||||
|
||||
## Next Phase
|
||||
|
||||
Proceed to [Phase 5: HTML Assembly](05-html-assembly.md) with captured screenshots.
|
||||
120
.claude/skills/software-manual/phases/05-html-assembly.md
Normal file
120
.claude/skills/software-manual/phases/05-html-assembly.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Phase 5: HTML Assembly
|
||||
|
||||
使用 `universal-executor` 子 Agent 生成最终 HTML,避免主 Agent 内存溢出。
|
||||
|
||||
## 核心原则
|
||||
|
||||
**主 Agent 负责编排,子 Agent 负责繁重计算。**
|
||||
|
||||
## 执行流程
|
||||
|
||||
```javascript
|
||||
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
|
||||
|
||||
// 委托给 universal-executor 执行 HTML 组装
|
||||
const result = Task({
|
||||
subagent_type: 'universal-executor',
|
||||
run_in_background: false,
|
||||
prompt: buildAssemblyPrompt(config, workDir)
|
||||
});
|
||||
|
||||
const buildResult = JSON.parse(result);
|
||||
```
|
||||
|
||||
## Prompt 构建
|
||||
|
||||
```javascript
|
||||
function buildAssemblyPrompt(config, workDir) {
|
||||
return `
|
||||
[ROLE] HTML Assembler
|
||||
|
||||
[TASK]
|
||||
生成 TiddlyWiki 风格的交互式 HTML 手册
|
||||
|
||||
[INPUT]
|
||||
- 模板: .claude/skills/software-manual/templates/tiddlywiki-shell.html
|
||||
- CSS: .claude/skills/software-manual/templates/css/wiki-base.css, wiki-dark.css
|
||||
- 配置: ${workDir}/manual-config.json
|
||||
- 章节: ${workDir}/sections/section-*.md
|
||||
- 截图: ${workDir}/screenshots/
|
||||
|
||||
[STEPS]
|
||||
1. 读取 HTML 模板和 CSS
|
||||
2. 逐个读取 section-*.md,转换为 HTML tiddlers
|
||||
3. 处理 <!-- SCREENSHOT: id="..." --> 标记,嵌入 Base64 图片
|
||||
4. 生成目录、搜索索引
|
||||
5. 组装最终 HTML,写入 ${workDir}/${config.software.name}-使用手册.html
|
||||
6. 生成构建报告 ${workDir}/build-report.json
|
||||
|
||||
[HTML FEATURES]
|
||||
- 搜索: 全文检索 + 高亮
|
||||
- 折叠: 章节可展开/收起
|
||||
- 标签: 分类过滤
|
||||
- 主题: 亮/暗模式切换
|
||||
- 离线: 所有资源内嵌
|
||||
|
||||
[RETURN JSON]
|
||||
{
|
||||
"status": "completed",
|
||||
"output_file": "${config.software.name}-使用手册.html",
|
||||
"file_size": "<size>",
|
||||
"sections_count": <n>,
|
||||
"screenshots_embedded": <n>
|
||||
}
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
## Agent 职责
|
||||
|
||||
1. **读取模板** → HTML + CSS
|
||||
2. **转换章节** → Markdown → HTML tiddlers
|
||||
3. **嵌入截图** → Base64 编码
|
||||
4. **生成索引** → 搜索数据
|
||||
5. **组装输出** → 单文件 HTML
|
||||
|
||||
## Markdown 转换规则
|
||||
|
||||
Agent 内部实现:
|
||||
|
||||
```
|
||||
# H1 → <h1>
|
||||
## H2 → <h2>
|
||||
### H3 → <h3>
|
||||
```code``` → <pre><code>
|
||||
**bold** → <strong>
|
||||
*italic* → <em>
|
||||
[text](url) → <a href>
|
||||
- item → <li>
|
||||
<!-- SCREENSHOT: id="xxx" --> → <figure><img src="data:..."></figure>
|
||||
```
|
||||
|
||||
## Tiddler 结构
|
||||
|
||||
```html
|
||||
<article class="tiddler" id="tiddler-{name}" data-tags="..." data-difficulty="...">
|
||||
<header class="tiddler-header">
|
||||
<h2><button class="collapse-toggle">▼</button> {title}</h2>
|
||||
<div class="tiddler-meta">{badges}</div>
|
||||
</header>
|
||||
<div class="tiddler-content">{html}</div>
|
||||
</article>
|
||||
```
|
||||
|
||||
## 输出
|
||||
|
||||
- `{软件名}-使用手册.html` - 最终 HTML
|
||||
- `build-report.json` - 构建报告
|
||||
|
||||
## 质量门禁
|
||||
|
||||
- [ ] HTML 渲染正确
|
||||
- [ ] 搜索功能可用
|
||||
- [ ] 折叠/展开正常
|
||||
- [ ] 主题切换持久化
|
||||
- [ ] 截图显示正确
|
||||
- [ ] 文件大小 < 10MB
|
||||
|
||||
## 下一阶段
|
||||
|
||||
→ [Phase 6: Iterative Refinement](06-iterative-refinement.md)
|
||||
259
.claude/skills/software-manual/phases/06-iterative-refinement.md
Normal file
259
.claude/skills/software-manual/phases/06-iterative-refinement.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# Phase 6: Iterative Refinement
|
||||
|
||||
Preview, collect feedback, and iterate until quality meets standards.
|
||||
|
||||
## Objective
|
||||
|
||||
- Preview generated HTML in browser
|
||||
- Collect user feedback
|
||||
- Address issues iteratively
|
||||
- Finalize documentation
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Preview HTML
|
||||
|
||||
```javascript
|
||||
const buildReport = JSON.parse(Read(`${workDir}/build-report.json`));
|
||||
const outputFile = `${workDir}/${buildReport.output}`;
|
||||
|
||||
// Open in default browser for preview
|
||||
Bash({ command: `start "${outputFile}"` }); // Windows
|
||||
// Bash({ command: `open "${outputFile}"` }); // macOS
|
||||
|
||||
// Report to user
|
||||
console.log(`
|
||||
📖 Manual Preview
|
||||
|
||||
File: ${buildReport.output}
|
||||
Size: ${buildReport.size_human}
|
||||
Sections: ${buildReport.sections}
|
||||
Screenshots: ${buildReport.screenshots}
|
||||
|
||||
Please review the manual in your browser.
|
||||
`);
|
||||
```
|
||||
|
||||
### Step 2: Collect Feedback
|
||||
|
||||
```javascript
|
||||
const feedback = await AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "How does the manual look overall?",
|
||||
header: "Overall",
|
||||
options: [
|
||||
{ label: "Looks great!", description: "Ready to finalize" },
|
||||
{ label: "Minor issues", description: "Small tweaks needed" },
|
||||
{ label: "Major issues", description: "Significant changes required" },
|
||||
{ label: "Missing content", description: "Need to add more sections" }
|
||||
],
|
||||
multiSelect: false
|
||||
},
|
||||
{
|
||||
question: "Which aspects need improvement? (Select all that apply)",
|
||||
header: "Improvements",
|
||||
options: [
|
||||
{ label: "Content accuracy", description: "Fix incorrect information" },
|
||||
{ label: "More examples", description: "Add more code examples" },
|
||||
{ label: "Better screenshots", description: "Retake or add screenshots" },
|
||||
{ label: "Styling/Layout", description: "Improve visual appearance" }
|
||||
],
|
||||
multiSelect: true
|
||||
}
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
### Step 3: Address Feedback
|
||||
|
||||
Based on feedback, take appropriate action:
|
||||
|
||||
#### Minor Issues
|
||||
|
||||
```javascript
|
||||
if (feedback.overall === "Minor issues") {
|
||||
// Prompt for specific changes
|
||||
const details = await AskUserQuestion({
|
||||
questions: [{
|
||||
question: "What specific changes are needed?",
|
||||
header: "Details",
|
||||
options: [
|
||||
{ label: "Typo fixes", description: "Fix spelling/grammar" },
|
||||
{ label: "Reorder sections", description: "Change section order" },
|
||||
{ label: "Update content", description: "Modify existing text" },
|
||||
{ label: "Custom changes", description: "I'll describe the changes" }
|
||||
],
|
||||
multiSelect: true
|
||||
}]
|
||||
});
|
||||
|
||||
// Apply changes based on user input
|
||||
applyMinorChanges(details);
|
||||
}
|
||||
```
|
||||
|
||||
#### Major Issues
|
||||
|
||||
```javascript
|
||||
if (feedback.overall === "Major issues") {
|
||||
// Return to relevant phase
|
||||
console.log(`
|
||||
Major issues require returning to an earlier phase:
|
||||
|
||||
- Content issues → Phase 3 (Parallel Analysis)
|
||||
- Screenshot issues → Phase 4 (Screenshot Capture)
|
||||
- Structure issues → Phase 2 (Project Exploration)
|
||||
|
||||
Which phase should we return to?
|
||||
`);
|
||||
|
||||
const phase = await selectPhase();
|
||||
return { action: 'restart', from_phase: phase };
|
||||
}
|
||||
```
|
||||
|
||||
#### Missing Content
|
||||
|
||||
```javascript
|
||||
if (feedback.overall === "Missing content") {
|
||||
// Identify missing sections
|
||||
const missing = await AskUserQuestion({
|
||||
questions: [{
|
||||
question: "What content is missing?",
|
||||
header: "Missing",
|
||||
options: [
|
||||
{ label: "API endpoints", description: "More API documentation" },
|
||||
{ label: "UI features", description: "Additional UI guides" },
|
||||
{ label: "Examples", description: "More code examples" },
|
||||
{ label: "Troubleshooting", description: "More FAQ items" }
|
||||
],
|
||||
multiSelect: true
|
||||
}]
|
||||
});
|
||||
|
||||
// Run additional agent(s) for missing content
|
||||
await runSupplementaryAgents(missing);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Save Iteration
|
||||
|
||||
```javascript
|
||||
// Save current version before changes
|
||||
const iterationNum = getNextIterationNumber(workDir);
|
||||
const iterationDir = `${workDir}/iterations`;
|
||||
|
||||
// Copy current version
|
||||
Bash({ command: `copy "${outputFile}" "${iterationDir}\\v${iterationNum}.html"` });
|
||||
|
||||
// Log iteration
|
||||
const iterationLog = {
|
||||
version: iterationNum,
|
||||
timestamp: new Date().toISOString(),
|
||||
feedback: feedback,
|
||||
changes: appliedChanges
|
||||
};
|
||||
|
||||
Write(`${iterationDir}/iteration-${iterationNum}.json`, JSON.stringify(iterationLog, null, 2));
|
||||
```
|
||||
|
||||
### Step 5: Regenerate if Needed
|
||||
|
||||
```javascript
|
||||
if (changesApplied) {
|
||||
// Re-run HTML assembly with updated sections
|
||||
await runPhase('05-html-assembly');
|
||||
|
||||
// Open updated preview
|
||||
Bash({ command: `start "${outputFile}"` });
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: Finalize
|
||||
|
||||
When user approves:
|
||||
|
||||
```javascript
|
||||
if (feedback.overall === "Looks great!") {
|
||||
// Final quality check
|
||||
const finalReport = {
|
||||
...buildReport,
|
||||
iterations: iterationNum,
|
||||
finalized_at: new Date().toISOString(),
|
||||
quality_score: calculateFinalQuality()
|
||||
};
|
||||
|
||||
Write(`${workDir}/final-report.json`, JSON.stringify(finalReport, null, 2));
|
||||
|
||||
// Suggest final location
|
||||
console.log(`
|
||||
✅ Manual Finalized!
|
||||
|
||||
Output: ${buildReport.output}
|
||||
Size: ${buildReport.size_human}
|
||||
Quality: ${finalReport.quality_score}%
|
||||
Iterations: ${iterationNum}
|
||||
|
||||
Suggested actions:
|
||||
1. Copy to project root: copy "${outputFile}" "docs/"
|
||||
2. Add to version control
|
||||
3. Publish to documentation site
|
||||
`);
|
||||
|
||||
return { status: 'completed', output: outputFile };
|
||||
}
|
||||
```
|
||||
|
||||
## Iteration History
|
||||
|
||||
Each iteration is logged:
|
||||
|
||||
```
|
||||
iterations/
|
||||
├── v1.html # First version
|
||||
├── iteration-1.json # Feedback and changes
|
||||
├── v2.html # After first iteration
|
||||
├── iteration-2.json # Feedback and changes
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Quality Metrics
|
||||
|
||||
Track improvement across iterations:
|
||||
|
||||
```javascript
|
||||
const qualityMetrics = {
|
||||
content_completeness: 0, // All sections present
|
||||
screenshot_coverage: 0, // Screenshots for all UI
|
||||
example_diversity: 0, // Different difficulty levels
|
||||
search_accuracy: 0, // Search returns relevant results
|
||||
user_satisfaction: 0 // Based on feedback
|
||||
};
|
||||
```
|
||||
|
||||
## Exit Conditions
|
||||
|
||||
The refinement phase ends when:
|
||||
1. User explicitly approves ("Looks great!")
|
||||
2. Maximum iterations reached (configurable, default: 5)
|
||||
3. Quality score exceeds threshold (default: 90%)
|
||||
|
||||
## Output
|
||||
|
||||
- **Final HTML**: `{软件名}-使用手册.html`
|
||||
- **Final Report**: `final-report.json`
|
||||
- **Iteration History**: `iterations/`
|
||||
|
||||
## Completion
|
||||
|
||||
When finalized, the skill is complete. Final output location:
|
||||
|
||||
```
|
||||
.workflow/.scratchpad/manual-{timestamp}/
|
||||
├── {软件名}-使用手册.html ← Final deliverable
|
||||
├── final-report.json
|
||||
└── iterations/
|
||||
```
|
||||
|
||||
Consider copying to a permanent location like `docs/` or project root.
|
||||
447
.claude/skills/software-manual/scripts/screenshot-helper.md
Normal file
447
.claude/skills/software-manual/scripts/screenshot-helper.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# Screenshot Helper
|
||||
|
||||
Guide for capturing screenshots using Chrome MCP.
|
||||
|
||||
## Overview
|
||||
|
||||
This script helps capture screenshots of web interfaces for the software manual using Chrome MCP or fallback methods.
|
||||
|
||||
## Chrome MCP Prerequisites
|
||||
|
||||
### Check MCP Availability
|
||||
|
||||
```javascript
|
||||
async function checkChromeMCPAvailability() {
|
||||
try {
|
||||
// Attempt to get Chrome version via MCP
|
||||
const version = await mcp__chrome__getVersion();
|
||||
return {
|
||||
available: true,
|
||||
browser: version.browser,
|
||||
version: version.version
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
available: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MCP Configuration
|
||||
|
||||
Expected Claude configuration for Chrome MCP:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"chrome": {
|
||||
"command": "npx",
|
||||
"args": ["@anthropic-ai/mcp-chrome"],
|
||||
"env": {
|
||||
"CHROME_PATH": "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Screenshot Workflow
|
||||
|
||||
### Step 1: Prepare Environment
|
||||
|
||||
```javascript
|
||||
async function prepareScreenshotEnvironment(workDir, config) {
|
||||
const screenshotDir = `${workDir}/screenshots`;
|
||||
|
||||
// Create directory
|
||||
Bash({ command: `mkdir -p "${screenshotDir}"` });
|
||||
|
||||
// Check Chrome MCP
|
||||
const chromeMCP = await checkChromeMCPAvailability();
|
||||
|
||||
if (!chromeMCP.available) {
|
||||
console.log('Chrome MCP not available. Will generate manual guide.');
|
||||
return { mode: 'manual' };
|
||||
}
|
||||
|
||||
// Start development server if needed
|
||||
if (config.screenshot_config?.dev_command) {
|
||||
const server = await startDevServer(config);
|
||||
return { mode: 'auto', server, screenshotDir };
|
||||
}
|
||||
|
||||
return { mode: 'auto', screenshotDir };
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Start Development Server
|
||||
|
||||
```javascript
|
||||
async function startDevServer(config) {
|
||||
const devCommand = config.screenshot_config.dev_command;
|
||||
const devUrl = config.screenshot_config.dev_url;
|
||||
|
||||
// Start server in background
|
||||
const server = Bash({
|
||||
command: devCommand,
|
||||
run_in_background: true
|
||||
});
|
||||
|
||||
console.log(`Starting dev server: ${devCommand}`);
|
||||
|
||||
// Wait for server to be ready
|
||||
const ready = await waitForServer(devUrl, 30000);
|
||||
|
||||
if (!ready) {
|
||||
throw new Error(`Server at ${devUrl} did not start in time`);
|
||||
}
|
||||
|
||||
console.log(`Dev server ready at ${devUrl}`);
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
async function waitForServer(url, timeout = 30000) {
|
||||
const start = Date.now();
|
||||
|
||||
while (Date.now() - start < timeout) {
|
||||
try {
|
||||
const response = await fetch(url, { method: 'HEAD' });
|
||||
if (response.ok) return true;
|
||||
} catch (e) {
|
||||
// Server not ready
|
||||
}
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Capture Screenshots
|
||||
|
||||
```javascript
|
||||
async function captureScreenshots(screenshots, config, workDir) {
|
||||
const results = {
|
||||
captured: [],
|
||||
failed: []
|
||||
};
|
||||
|
||||
const devUrl = config.screenshot_config.dev_url;
|
||||
const screenshotDir = `${workDir}/screenshots`;
|
||||
|
||||
for (const ss of screenshots) {
|
||||
try {
|
||||
// Build full URL
|
||||
const fullUrl = new URL(ss.url, devUrl).href;
|
||||
|
||||
console.log(`Capturing: ${ss.id} (${fullUrl})`);
|
||||
|
||||
// Configure capture options
|
||||
const options = {
|
||||
url: fullUrl,
|
||||
viewport: { width: 1280, height: 800 },
|
||||
fullPage: ss.fullPage || false
|
||||
};
|
||||
|
||||
// Wait for specific element if specified
|
||||
if (ss.wait_for) {
|
||||
options.waitFor = ss.wait_for;
|
||||
}
|
||||
|
||||
// Capture specific element if selector provided
|
||||
if (ss.selector) {
|
||||
options.selector = ss.selector;
|
||||
}
|
||||
|
||||
// Add delay for animations
|
||||
await sleep(500);
|
||||
|
||||
// Capture via Chrome MCP
|
||||
const result = await mcp__chrome__screenshot(options);
|
||||
|
||||
// Save as PNG
|
||||
const filename = `${ss.id}.png`;
|
||||
Write(`${screenshotDir}/${filename}`, result.data, { encoding: 'base64' });
|
||||
|
||||
results.captured.push({
|
||||
id: ss.id,
|
||||
file: filename,
|
||||
url: ss.url,
|
||||
description: ss.description,
|
||||
size: result.data.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Failed to capture ${ss.id}:`, error.message);
|
||||
results.failed.push({
|
||||
id: ss.id,
|
||||
url: ss.url,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 4: Generate Manifest
|
||||
|
||||
```javascript
|
||||
function generateScreenshotManifest(results, workDir) {
|
||||
const manifest = {
|
||||
generated: new Date().toISOString(),
|
||||
total: results.captured.length + results.failed.length,
|
||||
captured: results.captured.length,
|
||||
failed: results.failed.length,
|
||||
screenshots: results.captured,
|
||||
failures: results.failed
|
||||
};
|
||||
|
||||
Write(`${workDir}/screenshots/screenshots-manifest.json`,
|
||||
JSON.stringify(manifest, null, 2));
|
||||
|
||||
return manifest;
|
||||
}
|
||||
```
|
||||
|
||||
### Step 5: Cleanup
|
||||
|
||||
```javascript
|
||||
async function cleanupScreenshotEnvironment(env) {
|
||||
if (env.server) {
|
||||
console.log('Stopping dev server...');
|
||||
KillShell({ shell_id: env.server.task_id });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Main Runner
|
||||
|
||||
```javascript
|
||||
async function runScreenshotCapture(workDir, screenshots) {
|
||||
const config = JSON.parse(Read(`${workDir}/manual-config.json`));
|
||||
|
||||
// Prepare environment
|
||||
const env = await prepareScreenshotEnvironment(workDir, config);
|
||||
|
||||
if (env.mode === 'manual') {
|
||||
// Generate manual capture guide
|
||||
generateManualCaptureGuide(screenshots, workDir);
|
||||
return { success: false, mode: 'manual' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Capture screenshots
|
||||
const results = await captureScreenshots(screenshots, config, workDir);
|
||||
|
||||
// Generate manifest
|
||||
const manifest = generateScreenshotManifest(results, workDir);
|
||||
|
||||
// Generate manual guide for failed captures
|
||||
if (results.failed.length > 0) {
|
||||
generateManualCaptureGuide(results.failed, workDir);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
captured: results.captured.length,
|
||||
failed: results.failed.length,
|
||||
manifest
|
||||
};
|
||||
|
||||
} finally {
|
||||
// Cleanup
|
||||
await cleanupScreenshotEnvironment(env);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Manual Capture Fallback
|
||||
|
||||
When Chrome MCP is unavailable:
|
||||
|
||||
```javascript
|
||||
function generateManualCaptureGuide(screenshots, workDir) {
|
||||
const guide = `
|
||||
# Manual Screenshot Capture Guide
|
||||
|
||||
Chrome MCP is not available. Please capture screenshots manually.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Start your development server
|
||||
2. Open a browser
|
||||
3. Use a screenshot tool (Snipping Tool, Screenshot, etc.)
|
||||
|
||||
## Screenshots Required
|
||||
|
||||
${screenshots.map((ss, i) => `
|
||||
### ${i + 1}. ${ss.id}
|
||||
|
||||
- **URL**: ${ss.url}
|
||||
- **Description**: ${ss.description}
|
||||
- **Save as**: \`screenshots/${ss.id}.png\`
|
||||
${ss.selector ? `- **Capture area**: \`${ss.selector}\` element only` : '- **Type**: Full page or viewport'}
|
||||
${ss.wait_for ? `- **Wait for**: \`${ss.wait_for}\` to be visible` : ''}
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to ${ss.url}
|
||||
${ss.wait_for ? `2. Wait for ${ss.wait_for} to appear` : ''}
|
||||
${ss.selector ? `2. Capture only the ${ss.selector} area` : '2. Capture the full viewport'}
|
||||
3. Save as \`${ss.id}.png\`
|
||||
`).join('\n')}
|
||||
|
||||
## After Capturing
|
||||
|
||||
1. Place all PNG files in the \`screenshots/\` directory
|
||||
2. Ensure filenames match exactly (case-sensitive)
|
||||
3. Run Phase 5 (HTML Assembly) to continue
|
||||
|
||||
## Screenshot Specifications
|
||||
|
||||
- **Format**: PNG
|
||||
- **Width**: 1280px recommended
|
||||
- **Quality**: High
|
||||
- **Annotations**: None (add in post-processing if needed)
|
||||
`;
|
||||
|
||||
Write(`${workDir}/screenshots/MANUAL_CAPTURE.md`, guide);
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Options
|
||||
|
||||
### Viewport Sizes
|
||||
|
||||
```javascript
|
||||
const viewportPresets = {
|
||||
desktop: { width: 1280, height: 800 },
|
||||
tablet: { width: 768, height: 1024 },
|
||||
mobile: { width: 375, height: 667 },
|
||||
wide: { width: 1920, height: 1080 }
|
||||
};
|
||||
|
||||
async function captureResponsive(ss, config, workDir) {
|
||||
const results = [];
|
||||
|
||||
for (const [name, viewport] of Object.entries(viewportPresets)) {
|
||||
const result = await mcp__chrome__screenshot({
|
||||
url: ss.url,
|
||||
viewport
|
||||
});
|
||||
|
||||
const filename = `${ss.id}-${name}.png`;
|
||||
Write(`${workDir}/screenshots/${filename}`, result.data, { encoding: 'base64' });
|
||||
|
||||
results.push({ viewport: name, file: filename });
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
```
|
||||
|
||||
### Before/After Comparisons
|
||||
|
||||
```javascript
|
||||
async function captureInteraction(ss, config, workDir) {
|
||||
const baseUrl = config.screenshot_config.dev_url;
|
||||
const fullUrl = new URL(ss.url, baseUrl).href;
|
||||
|
||||
// Capture before state
|
||||
const before = await mcp__chrome__screenshot({
|
||||
url: fullUrl,
|
||||
viewport: { width: 1280, height: 800 }
|
||||
});
|
||||
Write(`${workDir}/screenshots/${ss.id}-before.png`, before.data, { encoding: 'base64' });
|
||||
|
||||
// Perform interaction (click, type, etc.)
|
||||
if (ss.interaction) {
|
||||
await mcp__chrome__click({ selector: ss.interaction.click });
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
// Capture after state
|
||||
const after = await mcp__chrome__screenshot({
|
||||
url: fullUrl,
|
||||
viewport: { width: 1280, height: 800 }
|
||||
});
|
||||
Write(`${workDir}/screenshots/${ss.id}-after.png`, after.data, { encoding: 'base64' });
|
||||
|
||||
return {
|
||||
before: `${ss.id}-before.png`,
|
||||
after: `${ss.id}-after.png`
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Screenshot Annotation
|
||||
|
||||
```javascript
|
||||
function generateAnnotationGuide(screenshots, workDir) {
|
||||
const guide = `
|
||||
# Screenshot Annotation Guide
|
||||
|
||||
For screenshots requiring callouts or highlights:
|
||||
|
||||
## Tools
|
||||
- macOS: Preview, Skitch
|
||||
- Windows: Snipping Tool, ShareX
|
||||
- Cross-platform: Greenshot, Lightshot
|
||||
|
||||
## Annotation Guidelines
|
||||
|
||||
1. **Callouts**: Use numbered circles (1, 2, 3)
|
||||
2. **Highlights**: Use semi-transparent rectangles
|
||||
3. **Arrows**: Point from text to element
|
||||
4. **Text**: Use sans-serif font, 12-14pt
|
||||
|
||||
## Color Scheme
|
||||
|
||||
- Primary: #0d6efd (blue)
|
||||
- Secondary: #6c757d (gray)
|
||||
- Success: #198754 (green)
|
||||
- Warning: #ffc107 (yellow)
|
||||
- Danger: #dc3545 (red)
|
||||
|
||||
## Screenshots Needing Annotation
|
||||
|
||||
${screenshots.filter(s => s.annotate).map(ss => `
|
||||
- **${ss.id}**: ${ss.description}
|
||||
- Highlight: ${ss.annotate.highlight || 'N/A'}
|
||||
- Callouts: ${ss.annotate.callouts?.join(', ') || 'N/A'}
|
||||
`).join('\n')}
|
||||
`;
|
||||
|
||||
Write(`${workDir}/screenshots/ANNOTATION_GUIDE.md`, guide);
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Chrome MCP Not Found
|
||||
|
||||
1. Check Claude MCP configuration
|
||||
2. Verify Chrome is installed
|
||||
3. Check CHROME_PATH environment variable
|
||||
|
||||
### Screenshots Are Blank
|
||||
|
||||
1. Increase wait time before capture
|
||||
2. Check if page requires authentication
|
||||
3. Verify URL is correct
|
||||
|
||||
### Elements Not Visible
|
||||
|
||||
1. Scroll element into view
|
||||
2. Expand collapsed sections
|
||||
3. Wait for animations to complete
|
||||
|
||||
### Server Not Starting
|
||||
|
||||
1. Check if port is already in use
|
||||
2. Verify dev command is correct
|
||||
3. Check for startup errors in logs
|
||||
419
.claude/skills/software-manual/scripts/swagger-runner.md
Normal file
419
.claude/skills/software-manual/scripts/swagger-runner.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# Swagger/OpenAPI Runner
|
||||
|
||||
Guide for generating backend API documentation from OpenAPI/Swagger specifications.
|
||||
|
||||
## Overview
|
||||
|
||||
This script extracts and converts OpenAPI/Swagger specifications to Markdown format for inclusion in the software manual.
|
||||
|
||||
## Detection Strategy
|
||||
|
||||
### Check for Existing Specification
|
||||
|
||||
```javascript
|
||||
async function detectOpenAPISpec() {
|
||||
// Check for existing spec files
|
||||
const specPatterns = [
|
||||
'openapi.json',
|
||||
'openapi.yaml',
|
||||
'openapi.yml',
|
||||
'swagger.json',
|
||||
'swagger.yaml',
|
||||
'swagger.yml',
|
||||
'**/openapi*.json',
|
||||
'**/swagger*.json'
|
||||
];
|
||||
|
||||
for (const pattern of specPatterns) {
|
||||
const files = Glob(pattern);
|
||||
if (files.length > 0) {
|
||||
return { found: true, type: 'file', path: files[0] };
|
||||
}
|
||||
}
|
||||
|
||||
// Check for swagger-jsdoc in dependencies
|
||||
const packageJson = JSON.parse(Read('package.json'));
|
||||
if (packageJson.dependencies?.['swagger-jsdoc'] ||
|
||||
packageJson.devDependencies?.['swagger-jsdoc']) {
|
||||
return { found: true, type: 'jsdoc' };
|
||||
}
|
||||
|
||||
// Check for NestJS Swagger
|
||||
if (packageJson.dependencies?.['@nestjs/swagger']) {
|
||||
return { found: true, type: 'nestjs' };
|
||||
}
|
||||
|
||||
// Check for runtime endpoint
|
||||
return { found: false, suggestion: 'runtime' };
|
||||
}
|
||||
```
|
||||
|
||||
## Extraction Methods
|
||||
|
||||
### Method A: From Existing Spec File
|
||||
|
||||
```javascript
|
||||
async function extractFromFile(specPath, workDir) {
|
||||
const outputDir = `${workDir}/api-docs/backend`;
|
||||
Bash({ command: `mkdir -p "${outputDir}"` });
|
||||
|
||||
// Copy spec to output
|
||||
Bash({ command: `cp "${specPath}" "${outputDir}/openapi.json"` });
|
||||
|
||||
// Convert to Markdown using widdershins
|
||||
const result = Bash({
|
||||
command: `npx widdershins "${specPath}" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL'`,
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
return { success: result.exitCode === 0, outputDir };
|
||||
}
|
||||
```
|
||||
|
||||
### Method B: From swagger-jsdoc
|
||||
|
||||
```javascript
|
||||
async function extractFromJsDoc(workDir) {
|
||||
const outputDir = `${workDir}/api-docs/backend`;
|
||||
|
||||
// Look for swagger definition file
|
||||
const defFiles = Glob('**/swagger*.js').concat(Glob('**/openapi*.js'));
|
||||
if (defFiles.length === 0) {
|
||||
return { success: false, error: 'No swagger definition found' };
|
||||
}
|
||||
|
||||
// Generate spec
|
||||
const result = Bash({
|
||||
command: `npx swagger-jsdoc -d "${defFiles[0]}" -o "${outputDir}/openapi.json"`,
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
return { success: false, error: result.stderr };
|
||||
}
|
||||
|
||||
// Convert to Markdown
|
||||
Bash({
|
||||
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'`
|
||||
});
|
||||
|
||||
return { success: true, outputDir };
|
||||
}
|
||||
```
|
||||
|
||||
### Method C: From NestJS Swagger
|
||||
|
||||
```javascript
|
||||
async function extractFromNestJS(workDir) {
|
||||
const outputDir = `${workDir}/api-docs/backend`;
|
||||
|
||||
// NestJS typically exposes /api-docs-json at runtime
|
||||
// We need to start the server temporarily
|
||||
|
||||
// Start server in background
|
||||
const server = Bash({
|
||||
command: 'npm run start:dev',
|
||||
run_in_background: true,
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
// Wait for server to be ready
|
||||
await waitForServer('http://localhost:3000', 30000);
|
||||
|
||||
// Fetch OpenAPI spec
|
||||
const spec = await fetch('http://localhost:3000/api-docs-json');
|
||||
const specJson = await spec.json();
|
||||
|
||||
// Save spec
|
||||
Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2));
|
||||
|
||||
// Stop server
|
||||
KillShell({ shell_id: server.task_id });
|
||||
|
||||
// Convert to Markdown
|
||||
Bash({
|
||||
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md" --language_tabs 'javascript:JavaScript' 'bash:cURL'`
|
||||
});
|
||||
|
||||
return { success: true, outputDir };
|
||||
}
|
||||
```
|
||||
|
||||
### Method D: From Runtime Endpoint
|
||||
|
||||
```javascript
|
||||
async function extractFromRuntime(workDir, serverUrl = 'http://localhost:3000') {
|
||||
const outputDir = `${workDir}/api-docs/backend`;
|
||||
|
||||
// Common OpenAPI endpoint paths
|
||||
const endpointPaths = [
|
||||
'/api-docs-json',
|
||||
'/swagger.json',
|
||||
'/openapi.json',
|
||||
'/docs/json',
|
||||
'/api/v1/docs.json'
|
||||
];
|
||||
|
||||
let specJson = null;
|
||||
|
||||
for (const path of endpointPaths) {
|
||||
try {
|
||||
const response = await fetch(`${serverUrl}${path}`);
|
||||
if (response.ok) {
|
||||
specJson = await response.json();
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!specJson) {
|
||||
return { success: false, error: 'Could not fetch OpenAPI spec from server' };
|
||||
}
|
||||
|
||||
// Save and convert
|
||||
Write(`${outputDir}/openapi.json`, JSON.stringify(specJson, null, 2));
|
||||
|
||||
Bash({
|
||||
command: `npx widdershins "${outputDir}/openapi.json" -o "${outputDir}/api-reference.md"`
|
||||
});
|
||||
|
||||
return { success: true, outputDir };
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Required Tools
|
||||
|
||||
```bash
|
||||
# For OpenAPI to Markdown conversion
|
||||
npm install -g widdershins
|
||||
|
||||
# Or as dev dependency
|
||||
npm install --save-dev widdershins
|
||||
|
||||
# For generating from JSDoc comments
|
||||
npm install --save-dev swagger-jsdoc
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### widdershins Options
|
||||
|
||||
```bash
|
||||
npx widdershins openapi.json \
|
||||
-o api-reference.md \
|
||||
--language_tabs 'javascript:JavaScript' 'python:Python' 'bash:cURL' \
|
||||
--summary \
|
||||
--omitHeader \
|
||||
--resolve \
|
||||
--expandBody
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--language_tabs` | Code example languages |
|
||||
| `--summary` | Use summary as operation heading |
|
||||
| `--omitHeader` | Don't include title header |
|
||||
| `--resolve` | Resolve $ref references |
|
||||
| `--expandBody` | Show full request body |
|
||||
|
||||
### swagger-jsdoc Definition
|
||||
|
||||
Example `swagger-def.js`:
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
definition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'MyApp API',
|
||||
version: '1.0.0',
|
||||
description: 'API documentation for MyApp'
|
||||
},
|
||||
servers: [
|
||||
{ url: 'http://localhost:3000/api/v1' }
|
||||
]
|
||||
},
|
||||
apis: ['./src/routes/*.js', './src/controllers/*.js']
|
||||
};
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### Generated Markdown Structure
|
||||
|
||||
```markdown
|
||||
# MyApp API
|
||||
|
||||
## Overview
|
||||
|
||||
Base URL: `http://localhost:3000/api/v1`
|
||||
|
||||
## Authentication
|
||||
|
||||
This API uses Bearer token authentication.
|
||||
|
||||
---
|
||||
|
||||
## Projects
|
||||
|
||||
### List Projects
|
||||
|
||||
`GET /projects`
|
||||
|
||||
Returns a list of all projects.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | In | Type | Required | Description |
|
||||
|------|-----|------|----------|-------------|
|
||||
| status | query | string | false | Filter by status |
|
||||
| page | query | integer | false | Page number |
|
||||
|
||||
**Responses**
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 200 | Successful response |
|
||||
| 401 | Unauthorized |
|
||||
|
||||
**Example Request**
|
||||
|
||||
```javascript
|
||||
fetch('/api/v1/projects?status=active')
|
||||
.then(res => res.json())
|
||||
.then(data => console.log(data));
|
||||
```
|
||||
|
||||
**Example Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{ "id": "1", "name": "Project 1" }
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"total": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### Main Runner
|
||||
|
||||
```javascript
|
||||
async function runSwaggerExtraction(workDir) {
|
||||
const detection = await detectOpenAPISpec();
|
||||
|
||||
if (!detection.found) {
|
||||
console.log('No OpenAPI spec detected. Skipping backend API docs.');
|
||||
return { success: false, skipped: true };
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
switch (detection.type) {
|
||||
case 'file':
|
||||
result = await extractFromFile(detection.path, workDir);
|
||||
break;
|
||||
case 'jsdoc':
|
||||
result = await extractFromJsDoc(workDir);
|
||||
break;
|
||||
case 'nestjs':
|
||||
result = await extractFromNestJS(workDir);
|
||||
break;
|
||||
default:
|
||||
result = await extractFromRuntime(workDir);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
// Post-process the Markdown
|
||||
await postProcessApiDocs(result.outputDir);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function postProcessApiDocs(outputDir) {
|
||||
const mdFile = `${outputDir}/api-reference.md`;
|
||||
let content = Read(mdFile);
|
||||
|
||||
// Remove widdershins header
|
||||
content = content.replace(/^---[\s\S]*?---\n/, '');
|
||||
|
||||
// Add custom styling hints
|
||||
content = content.replace(/^(#{1,3} .+)$/gm, '$1\n');
|
||||
|
||||
Write(mdFile, content);
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "widdershins: command not found"
|
||||
|
||||
```bash
|
||||
npm install -g widdershins
|
||||
# Or use npx
|
||||
npx widdershins openapi.json -o api.md
|
||||
```
|
||||
|
||||
#### "Error parsing OpenAPI spec"
|
||||
|
||||
```bash
|
||||
# Validate spec first
|
||||
npx @redocly/cli lint openapi.json
|
||||
|
||||
# Fix common issues
|
||||
npx @redocly/cli bundle openapi.json -o fixed.json
|
||||
```
|
||||
|
||||
#### "Server not responding"
|
||||
|
||||
Ensure the development server is running and accessible:
|
||||
|
||||
```bash
|
||||
# Check if server is running
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Check OpenAPI endpoint
|
||||
curl http://localhost:3000/api-docs-json
|
||||
```
|
||||
|
||||
### Manual Fallback
|
||||
|
||||
If automatic extraction fails, document APIs manually:
|
||||
|
||||
1. List all route files: `Glob('**/routes/*.js')`
|
||||
2. Extract route definitions using regex
|
||||
3. Build documentation structure manually
|
||||
|
||||
```javascript
|
||||
async function manualApiExtraction(workDir) {
|
||||
const routeFiles = Glob('src/routes/*.js').concat(Glob('src/routes/*.ts'));
|
||||
const endpoints = [];
|
||||
|
||||
for (const file of routeFiles) {
|
||||
const content = Read(file);
|
||||
const routes = content.matchAll(/router\.(get|post|put|delete|patch)\(['"]([^'"]+)['"]/g);
|
||||
|
||||
for (const match of routes) {
|
||||
endpoints.push({
|
||||
method: match[1].toUpperCase(),
|
||||
path: match[2],
|
||||
file: file
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
```
|
||||
357
.claude/skills/software-manual/scripts/typedoc-runner.md
Normal file
357
.claude/skills/software-manual/scripts/typedoc-runner.md
Normal file
@@ -0,0 +1,357 @@
|
||||
# TypeDoc Runner
|
||||
|
||||
Guide for generating frontend API documentation using TypeDoc.
|
||||
|
||||
## Overview
|
||||
|
||||
TypeDoc generates API documentation from TypeScript source code by analyzing type annotations and JSDoc comments.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Check TypeScript Project
|
||||
|
||||
```javascript
|
||||
// Verify TypeScript is used
|
||||
const packageJson = JSON.parse(Read('package.json'));
|
||||
const hasTypeScript = packageJson.devDependencies?.typescript ||
|
||||
packageJson.dependencies?.typescript;
|
||||
|
||||
if (!hasTypeScript) {
|
||||
console.log('Not a TypeScript project. Skipping TypeDoc.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for tsconfig.json
|
||||
const hasTsConfig = Glob('tsconfig.json').length > 0;
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Install TypeDoc
|
||||
|
||||
```bash
|
||||
npm install --save-dev typedoc typedoc-plugin-markdown
|
||||
```
|
||||
|
||||
### Optional Plugins
|
||||
|
||||
```bash
|
||||
# For better Markdown output
|
||||
npm install --save-dev typedoc-plugin-markdown
|
||||
|
||||
# For README inclusion
|
||||
npm install --save-dev typedoc-plugin-rename-defaults
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### typedoc.json
|
||||
|
||||
Create `typedoc.json` in project root:
|
||||
|
||||
```json
|
||||
{
|
||||
"entryPoints": ["./src/index.ts"],
|
||||
"entryPointStrategy": "expand",
|
||||
"out": ".workflow/.scratchpad/manual-{timestamp}/api-docs/frontend",
|
||||
"plugin": ["typedoc-plugin-markdown"],
|
||||
"exclude": [
|
||||
"**/node_modules/**",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.ts",
|
||||
"**/tests/**"
|
||||
],
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"excludeInternal": true,
|
||||
"hideGenerator": true,
|
||||
"readme": "none",
|
||||
"categorizeByGroup": true,
|
||||
"navigation": {
|
||||
"includeCategories": true,
|
||||
"includeGroups": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Alternative: CLI Options
|
||||
|
||||
```bash
|
||||
npx typedoc \
|
||||
--entryPoints src/index.ts \
|
||||
--entryPointStrategy expand \
|
||||
--out api-docs/frontend \
|
||||
--plugin typedoc-plugin-markdown \
|
||||
--exclude "**/node_modules/**" \
|
||||
--exclude "**/*.test.ts" \
|
||||
--excludePrivate \
|
||||
--excludeProtected \
|
||||
--readme none
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
### Basic Run
|
||||
|
||||
```javascript
|
||||
async function runTypeDoc(workDir) {
|
||||
const outputDir = `${workDir}/api-docs/frontend`;
|
||||
|
||||
// Create output directory
|
||||
Bash({ command: `mkdir -p "${outputDir}"` });
|
||||
|
||||
// Run TypeDoc
|
||||
const result = Bash({
|
||||
command: `npx typedoc --out "${outputDir}" --plugin typedoc-plugin-markdown src/`,
|
||||
timeout: 120000 // 2 minutes
|
||||
});
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
console.error('TypeDoc failed:', result.stderr);
|
||||
return { success: false, error: result.stderr };
|
||||
}
|
||||
|
||||
// List generated files
|
||||
const files = Glob(`${outputDir}/**/*.md`);
|
||||
console.log(`Generated ${files.length} documentation files`);
|
||||
|
||||
return { success: true, files };
|
||||
}
|
||||
```
|
||||
|
||||
### With Custom Entry Points
|
||||
|
||||
```javascript
|
||||
async function runTypeDocCustom(workDir, entryPoints) {
|
||||
const outputDir = `${workDir}/api-docs/frontend`;
|
||||
|
||||
// Build entry points string
|
||||
const entries = entryPoints.map(e => `--entryPoints "${e}"`).join(' ');
|
||||
|
||||
const result = Bash({
|
||||
command: `npx typedoc ${entries} --out "${outputDir}" --plugin typedoc-plugin-markdown`,
|
||||
timeout: 120000
|
||||
});
|
||||
|
||||
return { success: result.exitCode === 0 };
|
||||
}
|
||||
|
||||
// Example: Document specific files
|
||||
await runTypeDocCustom(workDir, [
|
||||
'src/api/index.ts',
|
||||
'src/hooks/index.ts',
|
||||
'src/utils/index.ts'
|
||||
]);
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
api-docs/frontend/
|
||||
├── README.md # Index
|
||||
├── modules.md # Module list
|
||||
├── modules/
|
||||
│ ├── api.md # API module
|
||||
│ ├── hooks.md # Hooks module
|
||||
│ └── utils.md # Utils module
|
||||
├── classes/
|
||||
│ ├── ApiClient.md # Class documentation
|
||||
│ └── ...
|
||||
├── interfaces/
|
||||
│ ├── Config.md # Interface documentation
|
||||
│ └── ...
|
||||
└── functions/
|
||||
├── formatDate.md # Function documentation
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Integration with Manual
|
||||
|
||||
### Reading TypeDoc Output
|
||||
|
||||
```javascript
|
||||
async function integrateTypeDocOutput(workDir) {
|
||||
const apiDocsDir = `${workDir}/api-docs/frontend`;
|
||||
const files = Glob(`${apiDocsDir}/**/*.md`);
|
||||
|
||||
// Build API reference content
|
||||
let content = '## Frontend API Reference\n\n';
|
||||
|
||||
// Add modules
|
||||
const modules = Glob(`${apiDocsDir}/modules/*.md`);
|
||||
for (const mod of modules) {
|
||||
const modContent = Read(mod);
|
||||
content += `### ${extractTitle(modContent)}\n\n`;
|
||||
content += summarizeModule(modContent);
|
||||
}
|
||||
|
||||
// Add functions
|
||||
const functions = Glob(`${apiDocsDir}/functions/*.md`);
|
||||
content += '\n### Functions\n\n';
|
||||
for (const fn of functions) {
|
||||
const fnContent = Read(fn);
|
||||
content += formatFunctionDoc(fnContent);
|
||||
}
|
||||
|
||||
// Add hooks
|
||||
const hooks = Glob(`${apiDocsDir}/functions/*Hook*.md`);
|
||||
if (hooks.length > 0) {
|
||||
content += '\n### Hooks\n\n';
|
||||
for (const hook of hooks) {
|
||||
const hookContent = Read(hook);
|
||||
content += formatHookDoc(hookContent);
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
```
|
||||
|
||||
### Example Output Format
|
||||
|
||||
```markdown
|
||||
## Frontend API Reference
|
||||
|
||||
### API Module
|
||||
|
||||
Functions for interacting with the backend API.
|
||||
|
||||
#### fetchProjects
|
||||
|
||||
```typescript
|
||||
function fetchProjects(options?: FetchOptions): Promise<Project[]>
|
||||
```
|
||||
|
||||
Fetches all projects for the current user.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| options | FetchOptions | Optional fetch configuration |
|
||||
|
||||
**Returns:** Promise<Project[]>
|
||||
|
||||
### Hooks
|
||||
|
||||
#### useProjects
|
||||
|
||||
```typescript
|
||||
function useProjects(options?: UseProjectsOptions): UseProjectsResult
|
||||
```
|
||||
|
||||
React hook for managing project data.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| options.status | string | Filter by project status |
|
||||
| options.limit | number | Max projects to fetch |
|
||||
|
||||
**Returns:**
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| projects | Project[] | Array of projects |
|
||||
| loading | boolean | Loading state |
|
||||
| error | Error \| null | Error if failed |
|
||||
| refetch | () => void | Refresh data |
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "Cannot find module 'typescript'"
|
||||
|
||||
```bash
|
||||
npm install --save-dev typescript
|
||||
```
|
||||
|
||||
#### "No entry points found"
|
||||
|
||||
Ensure entry points exist:
|
||||
|
||||
```bash
|
||||
# Check entry points
|
||||
ls src/index.ts
|
||||
|
||||
# Or use glob pattern
|
||||
npx typedoc --entryPoints "src/**/*.ts"
|
||||
```
|
||||
|
||||
#### "Unsupported TypeScript version"
|
||||
|
||||
```bash
|
||||
# Check TypeDoc compatibility
|
||||
npm info typedoc peerDependencies
|
||||
|
||||
# Install compatible version
|
||||
npm install --save-dev typedoc@0.25.x
|
||||
```
|
||||
|
||||
### Debugging
|
||||
|
||||
```bash
|
||||
# Verbose output
|
||||
npx typedoc --logLevel Verbose src/
|
||||
|
||||
# Show warnings
|
||||
npx typedoc --treatWarningsAsErrors src/
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Document Exports Only
|
||||
|
||||
```typescript
|
||||
// Good: Public API documented
|
||||
/**
|
||||
* Fetches projects from the API.
|
||||
* @param options - Fetch options
|
||||
* @returns Promise resolving to projects
|
||||
*/
|
||||
export function fetchProjects(options?: FetchOptions): Promise<Project[]> {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Internal: Not documented
|
||||
function internalHelper() {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Use JSDoc Comments
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* User hook for managing authentication state.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { user, login, logout } = useAuth();
|
||||
* ```
|
||||
*
|
||||
* @returns Authentication state and methods
|
||||
*/
|
||||
export function useAuth(): AuthResult {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Define Types Properly
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Configuration for the API client.
|
||||
*/
|
||||
export interface ApiConfig {
|
||||
/** API base URL */
|
||||
baseUrl: string;
|
||||
/** Request timeout in milliseconds */
|
||||
timeout?: number;
|
||||
/** Custom headers to include */
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
```
|
||||
325
.claude/skills/software-manual/specs/html-template.md
Normal file
325
.claude/skills/software-manual/specs/html-template.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# HTML Template Specification
|
||||
|
||||
Technical specification for the TiddlyWiki-style HTML output.
|
||||
|
||||
## Overview
|
||||
|
||||
The output is a single, self-contained HTML file with:
|
||||
- All CSS embedded inline
|
||||
- All JavaScript embedded inline
|
||||
- All images embedded as Base64
|
||||
- Full offline functionality
|
||||
|
||||
## File Structure
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{SOFTWARE_NAME}} - User Manual</title>
|
||||
<style>{{EMBEDDED_CSS}}</style>
|
||||
</head>
|
||||
<body class="wiki-container" data-theme="light">
|
||||
<aside class="wiki-sidebar">...</aside>
|
||||
<main class="wiki-content">...</main>
|
||||
<button class="theme-toggle">...</button>
|
||||
<script id="search-index" type="application/json">{{SEARCH_INDEX}}</script>
|
||||
<script>{{EMBEDDED_JS}}</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Placeholders
|
||||
|
||||
| Placeholder | Description | Source |
|
||||
|-------------|-------------|--------|
|
||||
| `{{SOFTWARE_NAME}}` | Software name | manual-config.json |
|
||||
| `{{VERSION}}` | Version number | manual-config.json |
|
||||
| `{{EMBEDDED_CSS}}` | All CSS content | wiki-base.css + wiki-dark.css |
|
||||
| `{{TOC_HTML}}` | Table of contents | Generated from sections |
|
||||
| `{{TIDDLERS_HTML}}` | All content blocks | Converted from Markdown |
|
||||
| `{{SEARCH_INDEX_JSON}}` | Search data | Generated from content |
|
||||
| `{{EMBEDDED_JS}}` | JavaScript code | Inline in template |
|
||||
| `{{TIMESTAMP}}` | Generation timestamp | ISO 8601 format |
|
||||
| `{{LOGO_BASE64}}` | Logo image | Project logo or generated |
|
||||
|
||||
## Component Specifications
|
||||
|
||||
### Sidebar (`.wiki-sidebar`)
|
||||
|
||||
```
|
||||
Width: 280px (fixed)
|
||||
Position: Fixed left
|
||||
Height: 100vh
|
||||
Components:
|
||||
- Logo area (.wiki-logo)
|
||||
- Search box (.wiki-search)
|
||||
- Tag navigation (.wiki-tags)
|
||||
- Table of contents (.wiki-toc)
|
||||
```
|
||||
|
||||
### Main Content (`.wiki-content`)
|
||||
|
||||
```
|
||||
Margin-left: 280px (sidebar width)
|
||||
Max-width: 900px (content)
|
||||
Components:
|
||||
- Header bar (.content-header)
|
||||
- Tiddler container (.tiddler-container)
|
||||
- Footer (.wiki-footer)
|
||||
```
|
||||
|
||||
### Tiddler (Content Block)
|
||||
|
||||
```html
|
||||
<article class="tiddler"
|
||||
id="tiddler-{{ID}}"
|
||||
data-tags="{{TAGS}}"
|
||||
data-difficulty="{{DIFFICULTY}}">
|
||||
<header class="tiddler-header">
|
||||
<h2 class="tiddler-title">
|
||||
<button class="collapse-toggle">▼</button>
|
||||
{{TITLE}}
|
||||
</h2>
|
||||
<div class="tiddler-meta">
|
||||
<span class="difficulty-badge {{DIFFICULTY}}">{{DIFFICULTY_LABEL}}</span>
|
||||
{{TAG_BADGES}}
|
||||
</div>
|
||||
</header>
|
||||
<div class="tiddler-content">
|
||||
{{CONTENT_HTML}}
|
||||
</div>
|
||||
</article>
|
||||
```
|
||||
|
||||
### Search Index Format
|
||||
|
||||
```json
|
||||
{
|
||||
"tiddler-overview": {
|
||||
"title": "Product Overview",
|
||||
"body": "Plain text content for searching...",
|
||||
"tags": ["getting-started", "overview"]
|
||||
},
|
||||
"tiddler-ui-guide": {
|
||||
"title": "UI Guide",
|
||||
"body": "Plain text content...",
|
||||
"tags": ["ui-guide"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Interactive Features
|
||||
|
||||
### 1. Search
|
||||
|
||||
- Full-text search with result highlighting
|
||||
- Searches title, body, and tags
|
||||
- Shows up to 10 results
|
||||
- Keyboard accessible (Enter to search, Esc to close)
|
||||
|
||||
### 2. Collapse/Expand
|
||||
|
||||
- Per-section toggle via button
|
||||
- Expand All / Collapse All buttons
|
||||
- State indicated by ▼ (expanded) or ▶ (collapsed)
|
||||
- Smooth transition animation
|
||||
|
||||
### 3. Tag Filtering
|
||||
|
||||
- Tags: all, getting-started, ui-guide, api, config, troubleshooting, examples
|
||||
- Single selection (radio behavior)
|
||||
- "all" shows everything
|
||||
- Hidden tiddlers via `display: none`
|
||||
|
||||
### 4. Theme Toggle
|
||||
|
||||
- Light/Dark mode switch
|
||||
- Persists to localStorage (`wiki-theme`)
|
||||
- Applies to entire document via `[data-theme="dark"]`
|
||||
- Toggle button shows sun/moon icon
|
||||
|
||||
### 5. Responsive Design
|
||||
|
||||
```
|
||||
Breakpoints:
|
||||
- Desktop (> 1024px): Sidebar visible
|
||||
- Tablet (768-1024px): Sidebar collapsible
|
||||
- Mobile (< 768px): Sidebar hidden, hamburger menu
|
||||
```
|
||||
|
||||
### 6. Print Support
|
||||
|
||||
- Hides sidebar, toggles, interactive elements
|
||||
- Expands all collapsed sections
|
||||
- Adjusts colors for print
|
||||
- Page breaks between sections
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
- Tab through interactive elements
|
||||
- Enter to activate buttons
|
||||
- Escape to close search results
|
||||
- Arrow keys in search results
|
||||
|
||||
### ARIA Attributes
|
||||
|
||||
```html
|
||||
<input aria-label="Search">
|
||||
<nav aria-label="Table of Contents">
|
||||
<button aria-label="Toggle theme">
|
||||
<div aria-live="polite"> <!-- For search results -->
|
||||
```
|
||||
|
||||
### Color Contrast
|
||||
|
||||
- Text/background ratio ≥ 4.5:1
|
||||
- Interactive elements clearly visible
|
||||
- Focus indicators visible
|
||||
|
||||
## Performance
|
||||
|
||||
### Target Metrics
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| Total file size | < 10MB |
|
||||
| Time to interactive | < 2s |
|
||||
| Search latency | < 100ms |
|
||||
|
||||
### Optimization Strategies
|
||||
|
||||
1. **Lazy loading for images**: `loading="lazy"`
|
||||
2. **Efficient search**: In-memory index, no external requests
|
||||
3. **CSS containment**: Scope styles to components
|
||||
4. **Minimal JavaScript**: Vanilla JS, no libraries
|
||||
|
||||
## CSS Variables
|
||||
|
||||
### Light Theme
|
||||
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8f9fa;
|
||||
--text-primary: #212529;
|
||||
--text-secondary: #495057;
|
||||
--accent-color: #0d6efd;
|
||||
--border-color: #dee2e6;
|
||||
}
|
||||
```
|
||||
|
||||
### Dark Theme
|
||||
|
||||
```css
|
||||
[data-theme="dark"] {
|
||||
--bg-primary: #1a1a2e;
|
||||
--bg-secondary: #16213e;
|
||||
--text-primary: #eaeaea;
|
||||
--text-secondary: #b8b8b8;
|
||||
--accent-color: #4dabf7;
|
||||
--border-color: #2d3748;
|
||||
}
|
||||
```
|
||||
|
||||
## Markdown to HTML Mapping
|
||||
|
||||
| Markdown | HTML |
|
||||
|----------|------|
|
||||
| `# Heading` | `<h1>` |
|
||||
| `## Heading` | `<h2>` |
|
||||
| `**bold**` | `<strong>` |
|
||||
| `*italic*` | `<em>` |
|
||||
| `` `code` `` | `<code>` |
|
||||
| `[link](url)` | `<a href="url">` |
|
||||
| `- item` | `<ul><li>` |
|
||||
| `1. item` | `<ol><li>` |
|
||||
| ``` ```js ``` | `<pre><code class="language-js">` |
|
||||
| `> quote` | `<blockquote>` |
|
||||
| `---` | `<hr>` |
|
||||
|
||||
## Screenshot Embedding
|
||||
|
||||
### Marker Format
|
||||
|
||||
```markdown
|
||||
<!-- SCREENSHOT: id="ss-login" url="/login" description="Login form" -->
|
||||
```
|
||||
|
||||
### Embedded Format
|
||||
|
||||
```html
|
||||
<figure class="screenshot">
|
||||
<img src="data:image/png;base64,{{BASE64_DATA}}"
|
||||
alt="Login form"
|
||||
loading="lazy">
|
||||
<figcaption>Login form</figcaption>
|
||||
</figure>
|
||||
```
|
||||
|
||||
### Placeholder (if missing)
|
||||
|
||||
```html
|
||||
<div class="screenshot-placeholder">
|
||||
[Screenshot: ss-login - Login form]
|
||||
</div>
|
||||
```
|
||||
|
||||
## File Size Optimization
|
||||
|
||||
### CSS
|
||||
|
||||
- Minify before embedding
|
||||
- Remove unused styles
|
||||
- Combine duplicate rules
|
||||
|
||||
### JavaScript
|
||||
|
||||
- Minify before embedding
|
||||
- Remove console.log statements
|
||||
- Use IIFE for scoping
|
||||
|
||||
### Images
|
||||
|
||||
- Compress before Base64 encoding
|
||||
- Use appropriate dimensions (max 1280px width)
|
||||
- Consider WebP format if browser support is acceptable
|
||||
|
||||
## Validation
|
||||
|
||||
### HTML Validation
|
||||
|
||||
- W3C HTML5 compliance
|
||||
- Proper nesting
|
||||
- Required attributes present
|
||||
|
||||
### CSS Validation
|
||||
|
||||
- Valid property values
|
||||
- No deprecated properties
|
||||
- Vendor prefixes where needed
|
||||
|
||||
### JavaScript
|
||||
|
||||
- No syntax errors
|
||||
- All functions defined
|
||||
- Error handling for edge cases
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Opens in Chrome/Firefox/Safari/Edge
|
||||
- [ ] Search works correctly
|
||||
- [ ] Collapse/expand works
|
||||
- [ ] Tag filtering works
|
||||
- [ ] Theme toggle works
|
||||
- [ ] Print preview correct
|
||||
- [ ] Keyboard navigation works
|
||||
- [ ] Mobile responsive
|
||||
- [ ] Offline functionality
|
||||
- [ ] All links valid
|
||||
- [ ] All images display
|
||||
- [ ] No console errors
|
||||
253
.claude/skills/software-manual/specs/quality-standards.md
Normal file
253
.claude/skills/software-manual/specs/quality-standards.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Quality Standards
|
||||
|
||||
Quality gates and standards for software manual generation.
|
||||
|
||||
## Quality Dimensions
|
||||
|
||||
### 1. Completeness (25%)
|
||||
|
||||
All required sections present and adequately covered.
|
||||
|
||||
| Requirement | Weight | Criteria |
|
||||
|-------------|--------|----------|
|
||||
| Overview section | 5 | Product intro, features, quick start |
|
||||
| UI Guide | 5 | All major screens documented |
|
||||
| API Reference | 5 | All public APIs documented |
|
||||
| Configuration | 4 | All config options explained |
|
||||
| Troubleshooting | 3 | Common issues addressed |
|
||||
| Examples | 3 | Multi-level examples provided |
|
||||
|
||||
**Scoring**:
|
||||
- 100%: All sections present with adequate depth
|
||||
- 80%: All sections present, some lacking depth
|
||||
- 60%: Missing 1-2 sections
|
||||
- 40%: Missing 3+ sections
|
||||
- 0%: Critical sections missing (overview, UI guide)
|
||||
|
||||
### 2. Consistency (25%)
|
||||
|
||||
Terminology, style, and structure uniform across sections.
|
||||
|
||||
| Aspect | Check |
|
||||
|--------|-------|
|
||||
| Terminology | Same term for same concept throughout |
|
||||
| Formatting | Consistent heading levels, code block styles |
|
||||
| Tone | Consistent formality level |
|
||||
| Cross-references | All internal links valid |
|
||||
| Screenshot naming | Follow `ss-{feature}-{action}` pattern |
|
||||
|
||||
**Scoring**:
|
||||
- 100%: Zero inconsistencies
|
||||
- 80%: 1-3 minor inconsistencies
|
||||
- 60%: 4-6 inconsistencies
|
||||
- 40%: 7-10 inconsistencies
|
||||
- 0%: Pervasive inconsistencies
|
||||
|
||||
### 3. Depth (25%)
|
||||
|
||||
Content provides sufficient detail for target audience.
|
||||
|
||||
| Level | Criteria |
|
||||
|-------|----------|
|
||||
| Shallow | Basic descriptions only |
|
||||
| Standard | Descriptions + usage examples |
|
||||
| Deep | Descriptions + examples + edge cases + best practices |
|
||||
|
||||
**Per-Section Depth Check**:
|
||||
- [ ] Explains "what" (definition)
|
||||
- [ ] Explains "why" (rationale)
|
||||
- [ ] Explains "how" (procedure)
|
||||
- [ ] Provides examples
|
||||
- [ ] Covers edge cases
|
||||
- [ ] Includes tips/best practices
|
||||
|
||||
**Scoring**:
|
||||
- 100%: Deep coverage on all critical sections
|
||||
- 80%: Standard coverage on all sections
|
||||
- 60%: Shallow coverage on some sections
|
||||
- 40%: Missing depth in critical areas
|
||||
- 0%: Superficial throughout
|
||||
|
||||
### 4. Readability (25%)
|
||||
|
||||
Clear, user-friendly writing that's easy to follow.
|
||||
|
||||
| Metric | Target |
|
||||
|--------|--------|
|
||||
| Sentence length | Average < 20 words |
|
||||
| Paragraph length | Average < 5 sentences |
|
||||
| Heading hierarchy | Proper H1 > H2 > H3 nesting |
|
||||
| Code blocks | Language specified |
|
||||
| Lists | Used for 3+ items |
|
||||
| Screenshots | Placed near relevant text |
|
||||
|
||||
**Structural Elements**:
|
||||
- [ ] Clear section headers
|
||||
- [ ] Numbered steps for procedures
|
||||
- [ ] Bullet lists for options/features
|
||||
- [ ] Tables for comparisons
|
||||
- [ ] Code blocks with syntax highlighting
|
||||
- [ ] Screenshots with captions
|
||||
|
||||
**Scoring**:
|
||||
- 100%: All readability criteria met
|
||||
- 80%: Minor structural issues
|
||||
- 60%: Some sections hard to follow
|
||||
- 40%: Significant readability problems
|
||||
- 0%: Unclear, poorly structured
|
||||
|
||||
## Overall Quality Score
|
||||
|
||||
```
|
||||
Overall = (Completeness × 0.25) + (Consistency × 0.25) +
|
||||
(Depth × 0.25) + (Readability × 0.25)
|
||||
```
|
||||
|
||||
**Quality Gates**:
|
||||
|
||||
| Gate | Threshold | Action |
|
||||
|------|-----------|--------|
|
||||
| Pass | ≥ 80% | Proceed to HTML generation |
|
||||
| Review | 60-79% | Address warnings, proceed with caution |
|
||||
| Fail | < 60% | Must address errors before continuing |
|
||||
|
||||
## Issue Classification
|
||||
|
||||
### Errors (Must Fix)
|
||||
|
||||
- Missing required sections
|
||||
- Invalid cross-references
|
||||
- Broken screenshot markers
|
||||
- Code blocks without language
|
||||
- Incomplete procedures (missing steps)
|
||||
|
||||
### Warnings (Should Fix)
|
||||
|
||||
- Terminology inconsistencies
|
||||
- Sections lacking depth
|
||||
- Missing examples
|
||||
- Long paragraphs (> 7 sentences)
|
||||
- Screenshots missing captions
|
||||
|
||||
### Info (Nice to Have)
|
||||
|
||||
- Optimization suggestions
|
||||
- Additional example opportunities
|
||||
- Alternative explanations
|
||||
- Enhancement ideas
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
### Pre-Generation
|
||||
|
||||
- [ ] All agents completed successfully
|
||||
- [ ] No errors in consolidation report
|
||||
- [ ] Overall score ≥ 60%
|
||||
|
||||
### Post-Generation
|
||||
|
||||
- [ ] HTML renders correctly
|
||||
- [ ] Search returns relevant results
|
||||
- [ ] All screenshots display
|
||||
- [ ] Theme toggle works
|
||||
- [ ] Print preview looks good
|
||||
|
||||
### Final Review
|
||||
|
||||
- [ ] User previewed and approved
|
||||
- [ ] File size reasonable (< 10MB)
|
||||
- [ ] No console errors in browser
|
||||
- [ ] Accessible (keyboard navigation works)
|
||||
|
||||
## Automated Checks
|
||||
|
||||
```javascript
|
||||
function runQualityChecks(workDir) {
|
||||
const results = {
|
||||
completeness: checkCompleteness(workDir),
|
||||
consistency: checkConsistency(workDir),
|
||||
depth: checkDepth(workDir),
|
||||
readability: checkReadability(workDir)
|
||||
};
|
||||
|
||||
results.overall = (
|
||||
results.completeness * 0.25 +
|
||||
results.consistency * 0.25 +
|
||||
results.depth * 0.25 +
|
||||
results.readability * 0.25
|
||||
);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function checkCompleteness(workDir) {
|
||||
const requiredSections = [
|
||||
'section-overview.md',
|
||||
'section-ui-guide.md',
|
||||
'section-api-reference.md',
|
||||
'section-configuration.md',
|
||||
'section-troubleshooting.md',
|
||||
'section-examples.md'
|
||||
];
|
||||
|
||||
const existing = Glob(`${workDir}/sections/section-*.md`);
|
||||
const found = requiredSections.filter(s =>
|
||||
existing.some(e => e.endsWith(s))
|
||||
);
|
||||
|
||||
return (found.length / requiredSections.length) * 100;
|
||||
}
|
||||
|
||||
function checkConsistency(workDir) {
|
||||
// Check terminology, cross-references, naming conventions
|
||||
const issues = [];
|
||||
|
||||
// ... implementation ...
|
||||
|
||||
return Math.max(0, 100 - issues.length * 10);
|
||||
}
|
||||
|
||||
function checkDepth(workDir) {
|
||||
// Check content length, examples, edge cases
|
||||
const sections = Glob(`${workDir}/sections/section-*.md`);
|
||||
let totalScore = 0;
|
||||
|
||||
for (const section of sections) {
|
||||
const content = Read(section);
|
||||
let sectionScore = 0;
|
||||
|
||||
if (content.length > 500) sectionScore += 20;
|
||||
if (content.includes('```')) sectionScore += 20;
|
||||
if (content.includes('Example')) sectionScore += 20;
|
||||
if (content.match(/\d+\./g)?.length > 3) sectionScore += 20;
|
||||
if (content.includes('Note:') || content.includes('Tip:')) sectionScore += 20;
|
||||
|
||||
totalScore += sectionScore;
|
||||
}
|
||||
|
||||
return totalScore / sections.length;
|
||||
}
|
||||
|
||||
function checkReadability(workDir) {
|
||||
// Check structure, formatting, organization
|
||||
const sections = Glob(`${workDir}/sections/section-*.md`);
|
||||
let issues = 0;
|
||||
|
||||
for (const section of sections) {
|
||||
const content = Read(section);
|
||||
|
||||
// Check heading hierarchy
|
||||
if (!content.startsWith('# ')) issues++;
|
||||
|
||||
// Check code block languages
|
||||
const codeBlocks = content.match(/```\w*/g);
|
||||
if (codeBlocks?.some(b => b === '```')) issues++;
|
||||
|
||||
// Check paragraph length
|
||||
const paragraphs = content.split('\n\n');
|
||||
if (paragraphs.some(p => p.split('. ').length > 7)) issues++;
|
||||
}
|
||||
|
||||
return Math.max(0, 100 - issues * 10);
|
||||
}
|
||||
```
|
||||
298
.claude/skills/software-manual/specs/writing-style.md
Normal file
298
.claude/skills/software-manual/specs/writing-style.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# Writing Style Guide
|
||||
|
||||
User-friendly writing standards for software manuals.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. User-Centered
|
||||
|
||||
Write for the user, not the developer.
|
||||
|
||||
**Do**:
|
||||
- "Click the **Save** button to save your changes"
|
||||
- "Enter your email address in the login form"
|
||||
|
||||
**Don't**:
|
||||
- "The onClick handler triggers the save mutation"
|
||||
- "POST to /api/auth/login with email in body"
|
||||
|
||||
### 2. Action-Oriented
|
||||
|
||||
Focus on what users can **do**, not what the system does.
|
||||
|
||||
**Do**:
|
||||
- "You can export your data as CSV"
|
||||
- "To create a new project, click **New Project**"
|
||||
|
||||
**Don't**:
|
||||
- "The system exports data in CSV format"
|
||||
- "A new project is created when the button is clicked"
|
||||
|
||||
### 3. Clear and Direct
|
||||
|
||||
Use simple, straightforward language.
|
||||
|
||||
**Do**:
|
||||
- "Select a file to upload"
|
||||
- "The maximum file size is 10MB"
|
||||
|
||||
**Don't**:
|
||||
- "Utilize the file selection interface to designate a file for uploading"
|
||||
- "File size constraints limit uploads to 10 megabytes"
|
||||
|
||||
## Tone
|
||||
|
||||
### Friendly but Professional
|
||||
|
||||
- Conversational but not casual
|
||||
- Helpful but not condescending
|
||||
- Confident but not arrogant
|
||||
|
||||
**Examples**:
|
||||
|
||||
| Too Casual | Just Right | Too Formal |
|
||||
|------------|------------|------------|
|
||||
| "Yo, here's how..." | "Here's how to..." | "The following procedure describes..." |
|
||||
| "Easy peasy!" | "That's all you need to do." | "The procedure has been completed." |
|
||||
| "Don't worry about it" | "You don't need to change this" | "This parameter does not require modification" |
|
||||
|
||||
### Second Person
|
||||
|
||||
Address the user directly as "you".
|
||||
|
||||
**Do**: "You can customize your dashboard..."
|
||||
**Don't**: "Users can customize their dashboards..."
|
||||
|
||||
## Structure
|
||||
|
||||
### Headings
|
||||
|
||||
Use clear, descriptive headings that tell users what they'll learn.
|
||||
|
||||
**Good Headings**:
|
||||
- "Getting Started"
|
||||
- "Creating Your First Project"
|
||||
- "Configuring Email Notifications"
|
||||
- "Troubleshooting Login Issues"
|
||||
|
||||
**Weak Headings**:
|
||||
- "Overview"
|
||||
- "Step 1"
|
||||
- "Settings"
|
||||
- "FAQ"
|
||||
|
||||
### Procedures
|
||||
|
||||
Number steps for sequential tasks.
|
||||
|
||||
```markdown
|
||||
## Creating a New User
|
||||
|
||||
1. Navigate to **Settings** > **Users**.
|
||||
2. Click the **Add User** button.
|
||||
3. Enter the user's email address.
|
||||
4. Select a role from the dropdown.
|
||||
5. Click **Save**.
|
||||
|
||||
The new user will receive an invitation email.
|
||||
```
|
||||
|
||||
### Features/Options
|
||||
|
||||
Use bullet lists for non-sequential items.
|
||||
|
||||
```markdown
|
||||
## Export Options
|
||||
|
||||
You can export your data in several formats:
|
||||
|
||||
- **CSV**: Compatible with spreadsheets
|
||||
- **JSON**: Best for developers
|
||||
- **PDF**: Ideal for sharing reports
|
||||
```
|
||||
|
||||
### Comparisons
|
||||
|
||||
Use tables for comparing options.
|
||||
|
||||
```markdown
|
||||
## Plan Comparison
|
||||
|
||||
| Feature | Free | Pro | Enterprise |
|
||||
|---------|------|-----|------------|
|
||||
| Projects | 3 | Unlimited | Unlimited |
|
||||
| Storage | 1GB | 10GB | 100GB |
|
||||
| Support | Community | Email | Dedicated |
|
||||
```
|
||||
|
||||
## Content Types
|
||||
|
||||
### Conceptual (What Is)
|
||||
|
||||
Explain what something is and why it matters.
|
||||
|
||||
```markdown
|
||||
## What is a Workspace?
|
||||
|
||||
A workspace is a container for your projects and team members. Each workspace
|
||||
has its own settings, billing, and permissions. You might create separate
|
||||
workspaces for different clients or departments.
|
||||
```
|
||||
|
||||
### Procedural (How To)
|
||||
|
||||
Step-by-step instructions for completing a task.
|
||||
|
||||
```markdown
|
||||
## How to Create a Workspace
|
||||
|
||||
1. Click your profile icon in the top-right corner.
|
||||
2. Select **Create Workspace**.
|
||||
3. Enter a name for your workspace.
|
||||
4. Choose a plan (you can upgrade later).
|
||||
5. Click **Create**.
|
||||
|
||||
Your new workspace is ready to use.
|
||||
```
|
||||
|
||||
### Reference (API/Config)
|
||||
|
||||
Detailed specifications and parameters.
|
||||
|
||||
```markdown
|
||||
## Configuration Options
|
||||
|
||||
### `DATABASE_URL`
|
||||
|
||||
- **Type**: String (required)
|
||||
- **Format**: `postgresql://user:password@host:port/database`
|
||||
- **Example**: `postgresql://admin:secret@localhost:5432/myapp`
|
||||
|
||||
Database connection string for PostgreSQL.
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
### Bold
|
||||
|
||||
Use for:
|
||||
- UI elements: Click **Save**
|
||||
- First use of key terms: **Workspaces** contain projects
|
||||
- Emphasis: **Never** share your API key
|
||||
|
||||
### Italic
|
||||
|
||||
Use for:
|
||||
- Introducing new terms: A *workspace* is...
|
||||
- Placeholders: Replace *your-api-key* with...
|
||||
- Emphasis (sparingly): This is *really* important
|
||||
|
||||
### Code
|
||||
|
||||
Use for:
|
||||
- Commands: Run `npm install`
|
||||
- File paths: Edit `config/settings.json`
|
||||
- Environment variables: Set `DATABASE_URL`
|
||||
- API endpoints: POST `/api/users`
|
||||
- Code references: The `handleSubmit` function
|
||||
|
||||
### Code Blocks
|
||||
|
||||
Always specify the language.
|
||||
|
||||
```javascript
|
||||
// Example: Fetching user data
|
||||
const response = await fetch('/api/user');
|
||||
const user = await response.json();
|
||||
```
|
||||
|
||||
### Notes and Warnings
|
||||
|
||||
Use for important callouts.
|
||||
|
||||
```markdown
|
||||
> **Note**: This feature requires a Pro plan.
|
||||
|
||||
> **Warning**: Deleting a workspace cannot be undone.
|
||||
|
||||
> **Tip**: Use keyboard shortcuts to work faster.
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||
### When to Include
|
||||
|
||||
- First time showing a UI element
|
||||
- Complex interfaces
|
||||
- Before/after comparisons
|
||||
- Error states
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Capture just the relevant area
|
||||
- Use consistent dimensions
|
||||
- Highlight important elements
|
||||
- Add descriptive captions
|
||||
|
||||
```markdown
|
||||
<!-- SCREENSHOT: id="ss-dashboard" description="Main dashboard showing project list" -->
|
||||
|
||||
*The dashboard displays all your projects with their status.*
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Good Section Example
|
||||
|
||||
```markdown
|
||||
## Inviting Team Members
|
||||
|
||||
You can invite colleagues to collaborate on your projects.
|
||||
|
||||
### To invite a team member:
|
||||
|
||||
1. Open **Settings** > **Team**.
|
||||
2. Click **Invite Member**.
|
||||
3. Enter their email address.
|
||||
4. Select their role:
|
||||
- **Admin**: Full access to all settings
|
||||
- **Editor**: Can edit projects
|
||||
- **Viewer**: Read-only access
|
||||
5. Click **Send Invite**.
|
||||
|
||||
The person will receive an email with a link to join your workspace.
|
||||
|
||||
> **Note**: You can have up to 5 team members on the Free plan.
|
||||
|
||||
<!-- SCREENSHOT: id="ss-invite-team" description="Team invitation dialog" -->
|
||||
```
|
||||
|
||||
## Language Guidelines
|
||||
|
||||
### Avoid Jargon
|
||||
|
||||
| Technical | User-Friendly |
|
||||
|-----------|---------------|
|
||||
| Execute | Run |
|
||||
| Terminate | Stop, End |
|
||||
| Instantiate | Create |
|
||||
| Invoke | Call, Use |
|
||||
| Parameterize | Set, Configure |
|
||||
| Persist | Save |
|
||||
|
||||
### Be Specific
|
||||
|
||||
| Vague | Specific |
|
||||
|-------|----------|
|
||||
| "Click the button" | "Click **Save**" |
|
||||
| "Enter information" | "Enter your email address" |
|
||||
| "An error occurred" | "Your password must be at least 8 characters" |
|
||||
| "It takes a moment" | "This typically takes 2-3 seconds" |
|
||||
|
||||
### Use Active Voice
|
||||
|
||||
| Passive | Active |
|
||||
|---------|--------|
|
||||
| "The file is uploaded" | "Upload the file" |
|
||||
| "Settings are saved" | "Click **Save** to keep your changes" |
|
||||
| "Errors are displayed" | "The form shows any errors" |
|
||||
695
.claude/skills/software-manual/templates/css/wiki-base.css
Normal file
695
.claude/skills/software-manual/templates/css/wiki-base.css
Normal file
@@ -0,0 +1,695 @@
|
||||
/* ========================================
|
||||
TiddlyWiki-Style Base CSS
|
||||
Software Manual Skill
|
||||
======================================== */
|
||||
|
||||
/* ========== CSS Variables ========== */
|
||||
:root {
|
||||
/* Light Theme */
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8f9fa;
|
||||
--bg-tertiary: #e9ecef;
|
||||
--text-primary: #212529;
|
||||
--text-secondary: #495057;
|
||||
--text-muted: #6c757d;
|
||||
--border-color: #dee2e6;
|
||||
--accent-color: #0d6efd;
|
||||
--accent-hover: #0b5ed7;
|
||||
--success-color: #198754;
|
||||
--warning-color: #ffc107;
|
||||
--danger-color: #dc3545;
|
||||
--info-color: #0dcaf0;
|
||||
|
||||
/* Layout */
|
||||
--sidebar-width: 280px;
|
||||
--header-height: 60px;
|
||||
--content-max-width: 900px;
|
||||
--spacing-xs: 0.25rem;
|
||||
--spacing-sm: 0.5rem;
|
||||
--spacing-md: 1rem;
|
||||
--spacing-lg: 1.5rem;
|
||||
--spacing-xl: 2rem;
|
||||
|
||||
/* Typography */
|
||||
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-family-mono: 'SF Mono', Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
--line-height: 1.6;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-base: 300ms ease;
|
||||
}
|
||||
|
||||
/* ========== Reset & Base ========== */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-base);
|
||||
line-height: var(--line-height);
|
||||
color: var(--text-primary);
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* ========== Layout ========== */
|
||||
.wiki-container {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* ========== Sidebar ========== */
|
||||
.wiki-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--sidebar-width);
|
||||
height: 100vh;
|
||||
background-color: var(--bg-primary);
|
||||
border-right: 1px solid var(--border-color);
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: transform var(--transition-base);
|
||||
}
|
||||
|
||||
/* Logo Area */
|
||||
.wiki-logo {
|
||||
padding: var(--spacing-lg);
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.wiki-logo .logo-placeholder {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin: 0 auto var(--spacing-sm);
|
||||
background: linear-gradient(135deg, var(--accent-color), var(--info-color));
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
.wiki-logo h1 {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.wiki-logo .version {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Search */
|
||||
.wiki-search {
|
||||
padding: var(--spacing-md);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wiki-search input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: var(--font-size-sm);
|
||||
background-color: var(--bg-secondary);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.wiki-search input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: var(--spacing-md);
|
||||
right: var(--spacing-md);
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.search-result-item {
|
||||
display: block;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
text-decoration: none;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
transition: background-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.search-result-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.search-result-item:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.result-excerpt {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.result-excerpt mark {
|
||||
background-color: var(--warning-color);
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
padding: var(--spacing-md);
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
.wiki-tags {
|
||||
padding: var(--spacing-md);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-xs);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.wiki-tags .tag {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.wiki-tags .tag:hover {
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.wiki-tags .tag.active {
|
||||
background-color: var(--accent-color);
|
||||
border-color: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Table of Contents */
|
||||
.wiki-toc {
|
||||
flex: 1;
|
||||
padding: var(--spacing-md);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.wiki-toc h3 {
|
||||
font-size: var(--font-size-sm);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.wiki-toc ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wiki-toc li {
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.wiki-toc a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-sm);
|
||||
color: var(--text-secondary);
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-size: var(--font-size-sm);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.wiki-toc a:hover {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* ========== Main Content ========== */
|
||||
.wiki-content {
|
||||
flex: 1;
|
||||
margin-left: var(--sidebar-width);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.content-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: var(--spacing-sm);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar-toggle span {
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 2px;
|
||||
background-color: var(--text-primary);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.header-actions button {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.header-actions button:hover {
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Tiddler Container */
|
||||
.tiddler-container {
|
||||
flex: 1;
|
||||
max-width: var(--content-max-width);
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-lg);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ========== Tiddler (Content Block) ========== */
|
||||
.tiddler {
|
||||
background-color: var(--bg-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.tiddler:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.tiddler-header {
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.tiddler-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.collapse-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
padding: var(--spacing-xs);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.tiddler.collapsed .collapse-toggle {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.tiddler-meta {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.difficulty-badge {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.difficulty-badge.beginner {
|
||||
background-color: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.difficulty-badge.intermediate {
|
||||
background-color: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.difficulty-badge.advanced {
|
||||
background-color: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.tag-badge {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: 0.75rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tiddler-content {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.tiddler.collapsed .tiddler-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ========== Content Typography ========== */
|
||||
.tiddler-content h1,
|
||||
.tiddler-content h2,
|
||||
.tiddler-content h3,
|
||||
.tiddler-content h4 {
|
||||
margin-top: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-md);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tiddler-content h1 { font-size: 1.75rem; }
|
||||
.tiddler-content h2 { font-size: 1.5rem; }
|
||||
.tiddler-content h3 { font-size: 1.25rem; }
|
||||
.tiddler-content h4 { font-size: 1.125rem; }
|
||||
|
||||
.tiddler-content p {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.tiddler-content ul,
|
||||
.tiddler-content ol {
|
||||
margin-bottom: var(--spacing-md);
|
||||
padding-left: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.tiddler-content li {
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.tiddler-content a {
|
||||
color: var(--accent-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tiddler-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Code */
|
||||
.tiddler-content code {
|
||||
font-family: var(--font-family-mono);
|
||||
font-size: 0.9em;
|
||||
padding: 2px 6px;
|
||||
background-color: var(--bg-tertiary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tiddler-content pre {
|
||||
position: relative;
|
||||
margin-bottom: var(--spacing-md);
|
||||
padding: var(--spacing-md);
|
||||
background-color: #1e1e1e;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.tiddler-content pre code {
|
||||
padding: 0;
|
||||
background: none;
|
||||
color: #d4d4d4;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.copy-code-btn {
|
||||
position: absolute;
|
||||
top: var(--spacing-sm);
|
||||
right: var(--spacing-sm);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: 0.75rem;
|
||||
background-color: var(--bg-tertiary);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.tiddler-content pre:hover .copy-code-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.tiddler-content table {
|
||||
width: 100%;
|
||||
margin-bottom: var(--spacing-md);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.tiddler-content th,
|
||||
.tiddler-content td {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border: 1px solid var(--border-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.tiddler-content th {
|
||||
background-color: var(--bg-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tiddler-content tr:nth-child(even) {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* Screenshots */
|
||||
.screenshot {
|
||||
margin: var(--spacing-lg) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.screenshot img {
|
||||
max-width: 100%;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.screenshot figcaption {
|
||||
margin-top: var(--spacing-sm);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.screenshot-placeholder {
|
||||
padding: var(--spacing-xl);
|
||||
background-color: var(--bg-tertiary);
|
||||
border: 2px dashed var(--border-color);
|
||||
border-radius: 8px;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== Footer ========== */
|
||||
.wiki-footer {
|
||||
padding: var(--spacing-lg);
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
border-top: 1px solid var(--border-color);
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* ========== Theme Toggle ========== */
|
||||
.theme-toggle {
|
||||
position: fixed;
|
||||
bottom: var(--spacing-lg);
|
||||
right: var(--spacing-lg);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background-color: var(--bg-primary);
|
||||
box-shadow: var(--shadow-lg);
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
z-index: 100;
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
[data-theme="light"] .moon-icon { display: inline; }
|
||||
[data-theme="light"] .sun-icon { display: none; }
|
||||
[data-theme="dark"] .moon-icon { display: none; }
|
||||
[data-theme="dark"] .sun-icon { display: inline; }
|
||||
|
||||
/* ========== Back to Top ========== */
|
||||
.back-to-top {
|
||||
position: fixed;
|
||||
bottom: calc(var(--spacing-lg) + 60px);
|
||||
right: var(--spacing-lg);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all var(--transition-fast);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.back-to-top.visible {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.back-to-top:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* ========== Responsive ========== */
|
||||
@media (max-width: 1024px) {
|
||||
.wiki-sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.wiki-sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.wiki-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.tiddler-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wiki-tags {
|
||||
overflow-x: auto;
|
||||
flex-wrap: nowrap;
|
||||
padding-bottom: var(--spacing-md);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== Print Styles ========== */
|
||||
@media print {
|
||||
.wiki-sidebar,
|
||||
.theme-toggle,
|
||||
.back-to-top,
|
||||
.content-header,
|
||||
.collapse-toggle,
|
||||
.copy-code-btn {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.wiki-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.tiddler {
|
||||
break-inside: avoid;
|
||||
box-shadow: none;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.tiddler.collapsed .tiddler-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tiddler-content pre {
|
||||
background-color: #f5f5f5 !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
}
|
||||
278
.claude/skills/software-manual/templates/css/wiki-dark.css
Normal file
278
.claude/skills/software-manual/templates/css/wiki-dark.css
Normal file
@@ -0,0 +1,278 @@
|
||||
/* ========================================
|
||||
TiddlyWiki-Style Dark Theme
|
||||
Software Manual Skill
|
||||
======================================== */
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Dark Theme Colors */
|
||||
--bg-primary: #1a1a2e;
|
||||
--bg-secondary: #16213e;
|
||||
--bg-tertiary: #0f3460;
|
||||
--text-primary: #eaeaea;
|
||||
--text-secondary: #b8b8b8;
|
||||
--text-muted: #888888;
|
||||
--border-color: #2d3748;
|
||||
--accent-color: #4dabf7;
|
||||
--accent-hover: #339af0;
|
||||
--success-color: #51cf66;
|
||||
--warning-color: #ffd43b;
|
||||
--danger-color: #ff6b6b;
|
||||
--info-color: #22b8cf;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Dark theme specific overrides */
|
||||
[data-theme="dark"] .wiki-logo .logo-placeholder {
|
||||
background: linear-gradient(135deg, var(--accent-color), #6741d9);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-search input {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-search input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .search-results {
|
||||
background-color: var(--bg-secondary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .search-result-item {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .search-result-item:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .result-excerpt mark {
|
||||
background-color: rgba(255, 212, 59, 0.3);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-tags .tag {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-tags .tag:hover {
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-tags .tag.active {
|
||||
background-color: var(--accent-color);
|
||||
border-color: var(--accent-color);
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-toc a:hover {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .content-header {
|
||||
background-color: var(--bg-primary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .sidebar-toggle span {
|
||||
background-color: var(--text-primary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .header-actions button {
|
||||
background-color: var(--bg-secondary);
|
||||
border-color: var(--border-color);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .header-actions button:hover {
|
||||
border-color: var(--accent-color);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler {
|
||||
background-color: var(--bg-primary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-header {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .difficulty-badge.beginner {
|
||||
background-color: rgba(81, 207, 102, 0.2);
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .difficulty-badge.intermediate {
|
||||
background-color: rgba(255, 212, 59, 0.2);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .difficulty-badge.advanced {
|
||||
background-color: rgba(255, 107, 107, 0.2);
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tag-badge {
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-content code {
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-content pre {
|
||||
background-color: #0d1117;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-content pre code {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .copy-code-btn {
|
||||
background-color: var(--bg-tertiary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-content th {
|
||||
background-color: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-content tr:nth-child(even) {
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-content th,
|
||||
[data-theme="dark"] .tiddler-content td {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .screenshot img {
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .screenshot-placeholder {
|
||||
background-color: var(--bg-tertiary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-footer {
|
||||
background-color: var(--bg-primary);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-toggle {
|
||||
background-color: var(--bg-secondary);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .back-to-top {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .back-to-top:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Scrollbar styling for dark theme */
|
||||
[data-theme="dark"] ::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] ::-webkit-scrollbar-track {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] ::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
/* Selection color */
|
||||
[data-theme="dark"] ::selection {
|
||||
background-color: rgba(77, 171, 247, 0.3);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Focus styles for accessibility */
|
||||
[data-theme="dark"] :focus {
|
||||
outline-color: var(--accent-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .wiki-search input:focus {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 3px rgba(77, 171, 247, 0.2);
|
||||
}
|
||||
|
||||
/* Link colors */
|
||||
[data-theme="dark"] .tiddler-content a {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tiddler-content a:hover {
|
||||
color: var(--accent-hover);
|
||||
}
|
||||
|
||||
/* Blockquote styling */
|
||||
[data-theme="dark"] .tiddler-content blockquote {
|
||||
border-left: 4px solid var(--accent-color);
|
||||
background-color: var(--bg-tertiary);
|
||||
padding: var(--spacing-md);
|
||||
margin: var(--spacing-md) 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Horizontal rule */
|
||||
[data-theme="dark"] .tiddler-content hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin: var(--spacing-lg) 0;
|
||||
}
|
||||
|
||||
/* Alert/Note boxes */
|
||||
[data-theme="dark"] .note,
|
||||
[data-theme="dark"] .warning,
|
||||
[data-theme="dark"] .tip,
|
||||
[data-theme="dark"] .danger {
|
||||
padding: var(--spacing-md);
|
||||
border-radius: 6px;
|
||||
margin: var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .note {
|
||||
background-color: rgba(34, 184, 207, 0.1);
|
||||
border-left: 4px solid var(--info-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .warning {
|
||||
background-color: rgba(255, 212, 59, 0.1);
|
||||
border-left: 4px solid var(--warning-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tip {
|
||||
background-color: rgba(81, 207, 102, 0.1);
|
||||
border-left: 4px solid var(--success-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .danger {
|
||||
background-color: rgba(255, 107, 107, 0.1);
|
||||
border-left: 4px solid var(--danger-color);
|
||||
}
|
||||
332
.claude/skills/software-manual/templates/tiddlywiki-shell.html
Normal file
332
.claude/skills/software-manual/templates/tiddlywiki-shell.html
Normal file
@@ -0,0 +1,332 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="{{SOFTWARE_NAME}} - Interactive Software Manual">
|
||||
<meta name="generator" content="software-manual-skill">
|
||||
<title>{{SOFTWARE_NAME}} v{{VERSION}} - User Manual</title>
|
||||
<style>
|
||||
{{EMBEDDED_CSS}}
|
||||
</style>
|
||||
</head>
|
||||
<body class="wiki-container" data-theme="light">
|
||||
<!-- Sidebar Navigation -->
|
||||
<aside class="wiki-sidebar">
|
||||
<!-- Logo and Title -->
|
||||
<div class="wiki-logo">
|
||||
<div class="logo-placeholder">{{SOFTWARE_NAME}}</div>
|
||||
<h1>{{SOFTWARE_NAME}}</h1>
|
||||
<span class="version">v{{VERSION}}</span>
|
||||
</div>
|
||||
|
||||
<!-- Search Box -->
|
||||
<div class="wiki-search">
|
||||
<input type="text" id="searchInput" placeholder="Search documentation..." aria-label="Search">
|
||||
<div id="searchResults" class="search-results" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
<!-- Tag Navigation -->
|
||||
<nav class="wiki-tags" aria-label="Filter by category">
|
||||
<button class="tag active" data-tag="all">All</button>
|
||||
<button class="tag" data-tag="getting-started">Getting Started</button>
|
||||
<button class="tag" data-tag="ui-guide">UI Guide</button>
|
||||
<button class="tag" data-tag="api">API</button>
|
||||
<button class="tag" data-tag="config">Configuration</button>
|
||||
<button class="tag" data-tag="troubleshooting">Troubleshooting</button>
|
||||
<button class="tag" data-tag="examples">Examples</button>
|
||||
</nav>
|
||||
|
||||
<!-- Table of Contents -->
|
||||
{{TOC_HTML}}
|
||||
</aside>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="wiki-content">
|
||||
<!-- Header Bar -->
|
||||
<header class="content-header">
|
||||
<button class="sidebar-toggle" id="sidebarToggle" aria-label="Toggle sidebar">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<div class="header-actions">
|
||||
<button class="expand-all" id="expandAll">Expand All</button>
|
||||
<button class="collapse-all" id="collapseAll">Collapse All</button>
|
||||
<button class="print-btn" id="printBtn">Print</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Tiddler Container -->
|
||||
<div class="tiddler-container">
|
||||
{{TIDDLERS_HTML}}
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="wiki-footer">
|
||||
<p>Generated by <strong>software-manual-skill</strong></p>
|
||||
<p>Last updated: <time datetime="{{TIMESTAMP}}">{{TIMESTAMP}}</time></p>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<!-- Theme Toggle Button -->
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
|
||||
<span class="sun-icon">☀</span>
|
||||
<span class="moon-icon">☾</span>
|
||||
</button>
|
||||
|
||||
<!-- Back to Top Button -->
|
||||
<button class="back-to-top" id="backToTop" aria-label="Back to top">↑</button>
|
||||
|
||||
<!-- Search Index Data -->
|
||||
<script id="search-index" type="application/json">
|
||||
{{SEARCH_INDEX_JSON}}
|
||||
</script>
|
||||
|
||||
<!-- Embedded JavaScript -->
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// ========== Search Functionality ==========
|
||||
class WikiSearch {
|
||||
constructor(indexData) {
|
||||
this.index = indexData;
|
||||
}
|
||||
|
||||
search(query) {
|
||||
if (!query || query.length < 2) return [];
|
||||
|
||||
const results = [];
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const queryWords = lowerQuery.split(/\s+/);
|
||||
|
||||
for (const [id, content] of Object.entries(this.index)) {
|
||||
let score = 0;
|
||||
|
||||
// Title match (higher weight)
|
||||
const titleLower = content.title.toLowerCase();
|
||||
if (titleLower.includes(lowerQuery)) {
|
||||
score += 10;
|
||||
}
|
||||
queryWords.forEach(word => {
|
||||
if (titleLower.includes(word)) score += 3;
|
||||
});
|
||||
|
||||
// Body match
|
||||
const bodyLower = content.body.toLowerCase();
|
||||
if (bodyLower.includes(lowerQuery)) {
|
||||
score += 5;
|
||||
}
|
||||
queryWords.forEach(word => {
|
||||
if (bodyLower.includes(word)) score += 1;
|
||||
});
|
||||
|
||||
// Tag match
|
||||
if (content.tags) {
|
||||
content.tags.forEach(tag => {
|
||||
if (tag.toLowerCase().includes(lowerQuery)) score += 4;
|
||||
});
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
results.push({
|
||||
id,
|
||||
title: content.title,
|
||||
excerpt: this.highlight(content.body, query),
|
||||
score
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, 10);
|
||||
}
|
||||
|
||||
highlight(text, query) {
|
||||
const maxLength = 150;
|
||||
const lowerText = text.toLowerCase();
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const index = lowerText.indexOf(lowerQuery);
|
||||
|
||||
if (index === -1) {
|
||||
return text.substring(0, maxLength) + (text.length > maxLength ? '...' : '');
|
||||
}
|
||||
|
||||
const start = Math.max(0, index - 40);
|
||||
const end = Math.min(text.length, index + query.length + 80);
|
||||
let excerpt = text.substring(start, end);
|
||||
|
||||
if (start > 0) excerpt = '...' + excerpt;
|
||||
if (end < text.length) excerpt += '...';
|
||||
|
||||
// Highlight matches
|
||||
const regex = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi');
|
||||
return excerpt.replace(regex, '<mark>$1</mark>');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize search
|
||||
const indexData = JSON.parse(document.getElementById('search-index').textContent);
|
||||
const search = new WikiSearch(indexData);
|
||||
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const searchResults = document.getElementById('searchResults');
|
||||
|
||||
searchInput.addEventListener('input', function() {
|
||||
const query = this.value.trim();
|
||||
const results = search.search(query);
|
||||
|
||||
if (results.length === 0) {
|
||||
searchResults.innerHTML = query.length >= 2
|
||||
? '<div class="no-results">No results found</div>'
|
||||
: '';
|
||||
return;
|
||||
}
|
||||
|
||||
searchResults.innerHTML = results.map(r => `
|
||||
<a href="#${r.id}" class="search-result-item" data-tiddler="${r.id}">
|
||||
<div class="result-title">${r.title}</div>
|
||||
<div class="result-excerpt">${r.excerpt}</div>
|
||||
</a>
|
||||
`).join('');
|
||||
});
|
||||
|
||||
// Clear search on result click
|
||||
searchResults.addEventListener('click', function(e) {
|
||||
const item = e.target.closest('.search-result-item');
|
||||
if (item) {
|
||||
searchInput.value = '';
|
||||
searchResults.innerHTML = '';
|
||||
|
||||
// Expand target tiddler
|
||||
const tiddlerId = item.dataset.tiddler;
|
||||
const tiddler = document.getElementById(tiddlerId);
|
||||
if (tiddler) {
|
||||
tiddler.classList.remove('collapsed');
|
||||
const toggle = tiddler.querySelector('.collapse-toggle');
|
||||
if (toggle) toggle.textContent = '▼';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ========== Collapse/Expand ==========
|
||||
document.querySelectorAll('.collapse-toggle').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const tiddler = this.closest('.tiddler');
|
||||
tiddler.classList.toggle('collapsed');
|
||||
this.textContent = tiddler.classList.contains('collapsed') ? '▶' : '▼';
|
||||
});
|
||||
});
|
||||
|
||||
// Expand/Collapse All
|
||||
document.getElementById('expandAll').addEventListener('click', function() {
|
||||
document.querySelectorAll('.tiddler').forEach(t => {
|
||||
t.classList.remove('collapsed');
|
||||
const toggle = t.querySelector('.collapse-toggle');
|
||||
if (toggle) toggle.textContent = '▼';
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('collapseAll').addEventListener('click', function() {
|
||||
document.querySelectorAll('.tiddler').forEach(t => {
|
||||
t.classList.add('collapsed');
|
||||
const toggle = t.querySelector('.collapse-toggle');
|
||||
if (toggle) toggle.textContent = '▶';
|
||||
});
|
||||
});
|
||||
|
||||
// ========== Tag Filtering ==========
|
||||
document.querySelectorAll('.wiki-tags .tag').forEach(tag => {
|
||||
tag.addEventListener('click', function() {
|
||||
const filter = this.dataset.tag;
|
||||
|
||||
// Update active state
|
||||
document.querySelectorAll('.wiki-tags .tag').forEach(t => t.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
// Filter tiddlers
|
||||
document.querySelectorAll('.tiddler').forEach(tiddler => {
|
||||
if (filter === 'all') {
|
||||
tiddler.style.display = '';
|
||||
} else {
|
||||
const tags = tiddler.dataset.tags || '';
|
||||
tiddler.style.display = tags.includes(filter) ? '' : 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ========== Theme Toggle ==========
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const savedTheme = localStorage.getItem('wiki-theme');
|
||||
|
||||
if (savedTheme) {
|
||||
document.body.dataset.theme = savedTheme;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('click', function() {
|
||||
const isDark = document.body.dataset.theme === 'dark';
|
||||
document.body.dataset.theme = isDark ? 'light' : 'dark';
|
||||
localStorage.setItem('wiki-theme', document.body.dataset.theme);
|
||||
});
|
||||
|
||||
// ========== Sidebar Toggle (Mobile) ==========
|
||||
document.getElementById('sidebarToggle').addEventListener('click', function() {
|
||||
document.querySelector('.wiki-sidebar').classList.toggle('open');
|
||||
});
|
||||
|
||||
// ========== Back to Top ==========
|
||||
const backToTop = document.getElementById('backToTop');
|
||||
|
||||
window.addEventListener('scroll', function() {
|
||||
backToTop.classList.toggle('visible', window.scrollY > 300);
|
||||
});
|
||||
|
||||
backToTop.addEventListener('click', function() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
});
|
||||
|
||||
// ========== Print ==========
|
||||
document.getElementById('printBtn').addEventListener('click', function() {
|
||||
window.print();
|
||||
});
|
||||
|
||||
// ========== TOC Navigation ==========
|
||||
document.querySelectorAll('.wiki-toc a').forEach(link => {
|
||||
link.addEventListener('click', function(e) {
|
||||
const tiddlerId = this.getAttribute('href').substring(1);
|
||||
const tiddler = document.getElementById(tiddlerId);
|
||||
|
||||
if (tiddler) {
|
||||
// Expand if collapsed
|
||||
tiddler.classList.remove('collapsed');
|
||||
const toggle = tiddler.querySelector('.collapse-toggle');
|
||||
if (toggle) toggle.textContent = '▼';
|
||||
|
||||
// Close sidebar on mobile
|
||||
document.querySelector('.wiki-sidebar').classList.remove('open');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ========== Code Block Copy ==========
|
||||
document.querySelectorAll('pre').forEach(pre => {
|
||||
const copyBtn = document.createElement('button');
|
||||
copyBtn.className = 'copy-code-btn';
|
||||
copyBtn.textContent = 'Copy';
|
||||
copyBtn.addEventListener('click', function() {
|
||||
const code = pre.querySelector('code');
|
||||
navigator.clipboard.writeText(code.textContent).then(() => {
|
||||
copyBtn.textContent = 'Copied!';
|
||||
setTimeout(() => copyBtn.textContent = 'Copy', 2000);
|
||||
});
|
||||
});
|
||||
pre.appendChild(copyBtn);
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user