Skip to content
Development
Skill

/html-prototype

可交互 HTML 原型,生成浏览器可打开的原型页面供用户查看和选择设计方案

From plugin
boss
54438 skills16 agents10 commands

Claude Code

$ npx -y skills add echoVic/boss-skill --skill html-prototype --agent claude-code

SKILL.md

html-prototype.SKILL.md
name: ui-designer/html-prototype
description: 可交互 HTML 原型,生成浏览器可打开的原型页面供用户查看和选择设计方案
version: 1.0.0
agent: ui-designer
type: workflow
user-invocable: false
agent-invocable: true
dependencies:
- ui-designer/design-system
  triggers:
- 用户需要直观看到设计效果而非纯文字描述时
- 存在多个设计变体需要用户对比选择时
- 设计方向存在分歧,需要可视化决策时
  metadata:
  internal: true

可交互 HTML 原型

适用场景

当设计方案需要用户**直观看到**才能决策时,生成可交互的 HTML 原型页面,在浏览器中展示并让用户点击选择。

**适用**:

  • 多设计变体需要用户对比选择
  • 布局/视觉方向需要用户确认
  • 复杂交互流程需要走查

**不适用**:

  • 纯后端/API 项目
  • 用户已明确指定设计方向
  • 简单页面无需可视化确认

核心流程

1. 征求同意 → 2. 生成原型 HTML → 3. 启动本地服务 → 4. 用户查看/选择 → 5. 读取选择 → 6. 清理

步骤 1:征求同意

在开始设计前,**单独发一条消息**询问用户:

> 接下来的设计方案如果能在浏览器里直观看到,会比纯文字描述更容易决策。我可以生成可交互的 HTML 原型页面,你打开本地链接就能看到效果并点击选择方案。需要开启吗?

**这条消息必须独立发送**,不要和其他内容混在一起。等待用户回复。

  • 用户同意 → 继续步骤 2
  • 用户拒绝 → 跳过 HTML 原型,用文字描述 + JSON 交付

步骤 2:生成原型 HTML

在 `.boss/<feature>/prototype/` 目录下生成原型文件。

**文件结构**:

.boss/<feature>/prototype/
├── index.html          # 主页面:展示所有变体,可点击选择
├── server.cjs          # 本地服务脚本(由 Agent 写入)
└── selections.json     # 用户选择记录(服务运行时生成)

2.1 写入服务脚本

将以下代码**原样写入** `.boss/<feature>/prototype/server.cjs`:

const http = require('http');
const fs = require('fs');
const path = require('path');

const DIR = __dirname;
const PORT = process.env.PROTO_PORT || 0;

const MIME = {
  '.html': 'text/html; charset=utf-8',
  '.css': 'text/css',
  '.js': 'application/javascript',
  '.json': 'application/json',
  '.svg': 'image/svg+xml',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
};

let selections = [];
const selFile = path.join(DIR, 'selections.json');
try {
  selections = JSON.parse(fs.readFileSync(selFile, 'utf-8'));
} catch (e) {}

function saveSelections() {
  fs.writeFileSync(selFile, JSON.stringify(selections, null, 2));
}

const clients = new Set();

const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/select') {
    let body = '';
    req.on('data', (c) => (body += c));
    req.on('end', () => {
      try {
        const sel = JSON.parse(body);
        sel.timestamp = Date.now();
        selections.push(sel);
        saveSelections();
        for (const c of clients) {
          try {
            c.write(`data: ${JSON.stringify(sel)}\n\n`);
          } catch (e) {}
        }
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ ok: true }));
      } catch (e) {
        res.writeHead(400);
        res.end('Bad request');
      }
    });
    return;
  }

  if (req.url === '/selections') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(selections));
    return;
  }

  if (req.url === '/events') {
    res.writeHead(200, {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    });
    clients.add(res);
    req.on('close', () => clients.delete(res));
    return;
  }

  let relPath = req.url === '/' ? '/index.html' : req.url.split('?')[0];
  let filePath = path.join(DIR, relPath);
  if (!filePath.startsWith(DIR)) {
    res.writeHead(403);
    res.end('Forbidden');
    return;
  }
  if (!fs.existsSync(filePath)) {
    res.writeHead(404);
    res.end('Not found');
    return;
  }
  const ext = path.extname(filePath).toLowerCase();
  res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
  res.end(fs.readFileSync(filePath));
});

server.listen(PORT, '127.0.0.1', () => {
  const port = server.address().port;
  console.log(JSON.stringify({ url: `http://localhost:${port}`, port }));
});

2.2 写入原型页面

写 `index.html`,使用以下骨架。**所有 CSS 内联**,不依赖外部资源。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>设计原型 · <feature></title>
<style>
  :root {
    --bg: #f8fafc;
    --card: #ffffff;
    --border: #e2e8f0;
    --text: #0f172a;
    --muted: #64748b;
    --primary: #3b82f6;
    --primary-hover: #2563eb;
    --success: #10b981;
    --radius: 12px;
    --shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
    --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1);
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); padding: 2rem 1rem; }
  .container { max-width: 1200px; margin: 0 auto; }
  .header { text-align: center; margin-bottom: 2rem; }
  .header h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
  .header p { color: var(--muted); font-size: 0.95rem; }
  .variants { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
  .variant {
    background: var(--card); border: 2px solid var(--border); border-radius: var(--radius);
    overflow: hidden; box-shadow: var(--shadow); transition: all 0.2s ease; cursor: pointer;
    display: flex; flex-direction: column;
  }
  .variant:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); border-color: var(--primary); }
  .variant.selected { border-color: var(--success); box-shadow: 0 0 0 3px rgba(16,185,129,0.2); }
  .variant-preview {
    background: #f1f5f9; padding: 1.5rem; min-height: 200px;
    display: flex; align-items: center; justify-content: center;
  }
  .variant-body { padding: 1rem 1.25rem; flex: 1; display: flex; flex-direction: column; }
  .variant-body h3 { font-size: 1.1rem; margin-bottom: 0.5rem; }
  .variant-body .concept { color: var(--muted); font-size: 0.9rem; margin-bottom: 0.75rem; }
  .variant-body .tradeoffs { font-size: 0.85rem; margin-bottom: 1rem; }
  .variant-body .tradeoffs .pros { color: #059669; }
  .variant-body .tradeoffs .cons { color: #dc2626; }
  .select-btn {
    margin-top: auto; padding: 0.6rem 1rem; background: var(--primary); color: #fff;
    border: none; border-radius: 8px; font-size: 0.9rem; font-weight: 500; cursor: pointer;
    transition: background 0.15s;
  }
  .select-b
Read more
@echovic
Ships withboss

Languages / 语言 / 言語 / 언어 / Idiomas / Langues: English · 中文 · 日本語 · 한국어 · Español · Français · Português This project is part of the AtomGit G-Star Incubation Program.

54438 skills16 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
544
Star history of echoVic/boss-skill on GitHub, a chart by star-history.com

Other skills on boss.

brainstorming
Skill

brainstorming

你是 Boss 的前置环节。用户通常只会丢过来一句话——可能是"帮我做个 XX"或者"我想搞个 XX"。你的工作是把这句话**翻译成下游流水线能跑起来的需求**。

@echovic@echovicInstall Skill

More skills from other plugins.

brainstorming
Skill
Popular
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
accessibility
Skill
Popular
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc1d293k
@affaan-m@affaan-mInstall Skill
compiler-commit
Skill
Popular

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about html-prototype.

Which coding agents can use html-prototype?
html-prototype installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.