Files
workbuddy_skills/product-planning/assets/diagram-design/assets/example-streamgraph-full.html
T
admin 8160f91559 技能库同步:另一台机的 2026-10-16 包 + element-picker 入包 + 工具归拢到技能级 assets/
product-planning(同步 26 改 65 新)
- 目录约定:docs/pm/<项目>/ ⇒ <工作区>/项目/<产品slug>/ 为产品根,下分 产品规划/ 与 产品原型/{designs,pages}
- ④段骨架六节 ⇒ 七节(新增「原型地址」;后两节改「页面导航」「原型状态取值」)
- ③段新增真机组件库;结案「源站只有暗色主题」,tokens.css(亮色)标为不可用于生产
- ③段改版规则反转为「一个产品只用一套原型、只留当前态」;②段新增 产品规划模板.md(表头唯一定义处)
- scripts:产品根定位重写(含 .registry/current-project 指针),新增 check_terms.py 与 selftest_product_root.py

session-mechanism(同步 3 改 2 新)
- references/06-技能优化规程.md 新增 §5「要求落在生成时看得见的面上」,总表 16 ⇒ 17 条;SKILL.md 同步
- scripts/collabctl.py 取回 _find_board() 候选定位(旧版写死 scripts/board.py,而看板已搬进 boards/会话看板/ ⇒ 旧版本就是坏的)

element-picker(新装)
- 按用户口径作「独立功能加入 product-planning」,非独立全局技能
- 修掉它自身三处路径漂移(技能根写法 ⇒ 同目录;README 两处旧落点;剪贴板示例文案与代码对齐)

工具归拢到技能级 assets/
- diagram-design / design-capture / element-picker / video-capture 从各段 assets/ 移到技能包 assets/
- 新增红线:本包有两处 assets/(技能级工具箱 vs 段内资产),引工具一律写「技能级 assets/<工具名>/」
- 路径口径由两类改三类(技能级工具箱 / 本段自带资产 / 同根技能)

不入库(磁盘保留)
- design-system-tiaoyue 整个目录(用户 2026-10-16 令):git rm -r --cached + .gitignore,理由=14 MB 且可 CDP 重采;
  已在 ③段 SKILL.md 注明「取仓后这一层是空的属正常,用自带套需另取或重采」
- element-picker 的运行期存档 marks/(服务自己会建目录)
2026-10-16 23:13:33 +08:00

130 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Build minutes by pipeline, twelve weeks · Streamgraph</title>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root { --color-paper:#f5f5f5; --color-paper-2:#ececec; --color-ink:#2d3142; --color-muted:#4f5d75; --color-soft:#7a8399; --color-rule:rgba(45,49,66,0.12); --color-accent:#eb6c36; --font-sans:'Geist',system-ui,sans-serif; --font-serif:'Instrument Serif',serif; --font-mono:'Geist Mono',ui-monospace,monospace; }
body { font-family: var(--font-sans); background: var(--color-paper); min-height: 100vh; padding: 3rem 2rem; color: var(--color-ink); }
.container { max-width: 1200px; margin: 0 auto; }
.header { margin-bottom: 2.5rem; }
.header-eyebrow { font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.75rem; }
h1 { font-family: var(--font-serif); font-size: clamp(1.75rem, 3vw + 1rem, 2.5rem); font-weight: 400; letter-spacing: -0.02em; line-height: 1.1; margin-bottom: 0.5rem; }
.subtitle { font-size: 1rem; line-height: 1.55; color: var(--color-muted); max-width: 58ch; }
.diagram-container { background: var(--color-paper-2); border-radius: 8px; border: 1px solid var(--color-rule); padding: 1.5rem; overflow-x: auto; }
svg { width: 100%; min-width: 760px; display: block; }
.cards { display: grid; grid-template-columns: 1.1fr 1fr 0.9fr; gap: 1rem; margin-top: 1.5rem; }
@media (max-width: 720px) { .cards { grid-template-columns: 1fr; } }
.card { background: #fff; border-radius: 6px; border: 1px solid var(--color-rule); padding: 1.25rem; }
.card .eyebrow { font-family: var(--font-mono); font-size: 0.5rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--color-muted); margin-bottom: 0.5rem; }
.card-header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.875rem; padding-bottom: 0.875rem; border-bottom: 1px solid rgba(45,49,66,0.08); }
.card-dot { width: 7px; height: 7px; border-radius: 50%; }
.card-dot.coral { background: var(--color-accent); }
.card-dot.ink-strong { background: rgba(45,49,66,0.78); }
.card-dot.ink-light { background: rgba(45,49,66,0.30); }
.card h3 { font-size: 0.875rem; font-weight: 600; }
.card p { color: var(--color-muted); font-size: 0.8125rem; line-height: 1.55; }
.footer { margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid rgba(45,49,66,0.10); font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.06em; color: var(--color-muted); display: flex; justify-content: space-between; flex-wrap: wrap; gap: 0.5rem; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<p class="header-eyebrow">Streamgraph · Diagram Design</p>
<h1>Where the extra build minutes went</h1>
<p class="subtitle">Weekly CI build minutes per pipeline, stacked on a symmetric baseline so the height of the whole stream is the week's total. The build roughly doubled in twelve weeks, from 124 to 278 minutes, and almost all of the growth is one layer: the end-to-end suite, up from 12 to 118 minutes a week.</p>
</div>
<div class="diagram-container">
<svg viewBox="0 0 1000 500" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="streamgraph-full-title streamgraph-full-desc">
<title id="streamgraph-full-title">Where the extra build minutes went</title>
<desc id="streamgraph-full-desc">Streamgraph of weekly CI build minutes by pipeline across twelve weeks, stacked on a symmetric baseline so the envelope height reads as the total; the end-to-end suite grows from 12 to 118 minutes a week and passes the unit suite in week eight, while the docs pipeline pauses to zero in weeks five and six.</desc>
<defs>
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="0.9" fill="rgba(45,49,66,0.10)"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="#f5f5f5"/>
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
<!-- Symmetric baseline: every period is centred on y=230, so the top and
bottom of the envelope always average to the midline and the envelope
height IS the weekly total at 1.25px per minute. Boundaries are
Catmull-Rom smoothed (control points at 1/6 chord) with every vertex
on its true stacked value; scripts/verify-streamgraph.py enforces
both against the values each layer declares. -->
<text transform="rotate(-90 24 230)" x="24" y="230" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">BUILD MINUTES PER WEEK</text>
<!-- No y-axis ticks: the form is about the shape of the envelope and the
layers inside it; per-pipeline totals are printed in the legend. -->
<path data-layer="Docs" data-values="8,9,7,6,0,0,5,7,8,8,9,9" d="M 80 297.5 C 93.3 298.1, 133.3 299.4, 160 301.2 C 186.7 303, 213.3 305.9, 240 308.1 C 266.7 310.3, 293.3 311.3, 320 314.4 C 346.7 317.5, 373.3 323, 400 326.9 C 426.7 330.8, 453.3 334.9, 480 337.5 C 506.7 340.1, 533.3 339.7, 560 342.5 C 586.7 345.3, 613.3 350.8, 640 354.4 C 666.7 358, 693.3 361.4, 720 364.4 C 746.7 367.4, 773.3 369.4, 800 372.5 C 826.7 375.6, 853.3 379.8, 880 383.1 C 906.7 386.4, 946.7 390.9, 960 392.5 L 960 403.8 C 946.7 402.2, 906.7 397.9, 880 394.4 C 853.3 390.8, 826.7 385.8, 800 382.5 C 773.3 379.2, 746.7 377.6, 720 374.4 C 693.3 371.2, 666.7 367.4, 640 363.1 C 613.3 358.8, 586.7 353.1, 560 348.8 C 533.3 344.5, 506.7 341.1, 480 337.5 C 453.3 333.9, 426.7 329.5, 400 326.9 C 373.3 324.3, 346.7 323.6, 320 321.9 C 293.3 320.2, 266.7 318.5, 240 316.9 C 213.3 315.3, 186.7 314.1, 160 312.5 C 133.3 310.9, 93.3 308.3, 80 307.5 Z" fill="rgba(45,49,66,0.30)" stroke="#f5f5f5" stroke-width="1"/>
<path data-layer="Integration" data-values="34,36,38,42,44,47,49,52,54,56,59,61" d="M 80 255 C 93.3 255.2, 133.3 255.3, 160 256.2 C 186.7 257.1, 213.3 259.7, 240 260.6 C 266.7 261.6, 293.3 260, 320 261.9 C 346.7 263.8, 373.3 269.1, 400 271.9 C 426.7 274.7, 453.3 277.2, 480 278.8 C 506.7 280.4, 533.3 279.4, 560 281.2 C 586.7 283, 613.3 286.8, 640 289.4 C 666.7 292, 693.3 294.7, 720 296.9 C 746.7 299.1, 773.3 300.4, 800 302.5 C 826.7 304.6, 853.3 307.1, 880 309.4 C 906.7 311.7, 946.7 315.1, 960 316.2 L 960 392.5 C 946.7 390.9, 906.7 386.4, 880 383.1 C 853.3 379.8, 826.7 375.6, 800 372.5 C 773.3 369.4, 746.7 367.4, 720 364.4 C 693.3 361.4, 666.7 358, 640 354.4 C 613.3 350.8, 586.7 345.3, 560 342.5 C 533.3 339.7, 506.7 340.1, 480 337.5 C 453.3 334.9, 426.7 330.8, 400 326.9 C 373.3 323, 346.7 317.5, 320 314.4 C 293.3 311.3, 266.7 310.3, 240 308.1 C 213.3 305.9, 186.7 303, 160 301.2 C 133.3 299.4, 93.3 298.1, 80 297.5 Z" fill="rgba(45,49,66,0.62)" stroke="#f5f5f5" stroke-width="1"/>
<path data-layer="Unit" data-values="58,60,62,61,64,66,65,68,70,69,72,74" d="M 80 182.5 C 93.3 182.3, 133.3 181.1, 160 181.2 C 186.7 181.3, 213.3 182.4, 240 183.1 C 266.7 183.8, 293.3 184.1, 320 185.6 C 346.7 187.1, 373.3 190.1, 400 191.9 C 426.7 193.7, 453.3 194.8, 480 196.2 C 506.7 197.5, 533.3 198.6, 560 200 C 586.7 201.4, 613.3 202.8, 640 204.4 C 666.7 206, 693.3 207.4, 720 209.4 C 746.7 211.4, 773.3 214.5, 800 216.2 C 826.7 217.9, 853.3 218.1, 880 219.4 C 906.7 220.7, 946.7 223.1, 960 223.8 L 960 316.2 C 946.7 315.1, 906.7 311.7, 880 309.4 C 853.3 307.1, 826.7 304.6, 800 302.5 C 773.3 300.4, 746.7 299.1, 720 296.9 C 693.3 294.7, 666.7 292, 640 289.4 C 613.3 286.8, 586.7 283, 560 281.2 C 533.3 279.4, 506.7 280.4, 480 278.8 C 453.3 277.2, 426.7 274.7, 400 271.9 C 373.3 269.1, 346.7 263.8, 320 261.9 C 293.3 260, 266.7 261.6, 240 260.6 C 213.3 259.7, 186.7 257.1, 160 256.2 C 133.3 255.3, 93.3 255.2, 80 255 Z" fill="rgba(45,49,66,0.78)" stroke="#f5f5f5" stroke-width="1"/>
<!-- End-to-end FOCAL: the accent marks the editorially focal layer --
the pipeline whose growth is the story -- and it is also the one
eating the budget. Every other layer rides the ink-opacity ramp,
strongest tone on the largest pipeline. -->
<path data-layer="End-to-end" data-values="12,15,19,26,34,45,58,72,85,96,108,118" d="M 80 167.5 C 93.3 166.7, 133.3 163.8, 160 162.5 C 186.7 161.2, 213.3 161, 240 159.4 C 266.7 157.8, 293.3 154.8, 320 153.1 C 346.7 151.4, 373.3 151.6, 400 149.4 C 426.7 147.2, 453.3 143.7, 480 140 C 506.7 136.3, 533.3 131.8, 560 127.5 C 586.7 123.2, 613.3 118.5, 640 114.4 C 666.7 110.3, 693.3 106.1, 720 103.1 C 746.7 100.1, 773.3 99.3, 800 96.2 C 826.7 93.1, 853.3 87.7, 880 84.4 C 906.7 81.1, 946.7 77.6, 960 76.2 L 960 223.8 C 946.7 223.1, 906.7 220.7, 880 219.4 C 853.3 218.1, 826.7 217.9, 800 216.2 C 773.3 214.5, 746.7 211.4, 720 209.4 C 693.3 207.4, 666.7 206, 640 204.4 C 613.3 202.8, 586.7 201.4, 560 200 C 533.3 198.6, 506.7 197.5, 480 196.2 C 453.3 194.8, 426.7 193.7, 400 191.9 C 373.3 190.1, 346.7 187.1, 320 185.6 C 293.3 184.1, 266.7 183.8, 240 183.1 C 213.3 182.4, 186.7 181.3, 160 181.2 C 133.3 181.1, 93.3 182.3, 80 182.5 Z" fill="#eb6c36" stroke="#f5f5f5" stroke-width="1"/>
<path data-layer="Lint" data-values="12,12,13,12,13,14,13,14,14,15,15,16" d="M 80 152.5 C 93.3 151.7, 133.3 149.1, 160 147.5 C 186.7 145.9, 213.3 144.7, 240 143.1 C 266.7 141.5, 293.3 139.8, 320 138.1 C 346.7 136.4, 373.3 135.7, 400 133.1 C 426.7 130.5, 453.3 126.2, 480 122.5 C 506.7 118.8, 533.3 115.5, 560 111.2 C 586.7 106.9, 613.3 101.2, 640 96.9 C 666.7 92.6, 693.3 88.8, 720 85.6 C 746.7 82.4, 773.3 80.8, 800 77.5 C 826.7 74.2, 853.3 69.1, 880 65.6 C 906.7 62, 946.7 57.8, 960 56.2 L 960 76.2 C 946.7 77.6, 906.7 81.1, 880 84.4 C 853.3 87.7, 826.7 93.1, 800 96.2 C 773.3 99.3, 746.7 100.1, 720 103.1 C 693.3 106.1, 666.7 110.3, 640 114.4 C 613.3 118.5, 586.7 123.2, 560 127.5 C 533.3 131.8, 506.7 136.3, 480 140 C 453.3 143.7, 426.7 147.2, 400 149.4 C 373.3 151.6, 346.7 151.4, 320 153.1 C 293.3 154.8, 266.7 157.8, 240 159.4 C 213.3 161, 186.7 161.2, 160 162.5 C 133.3 163.8, 93.3 166.7, 80 167.5 Z" fill="rgba(45,49,66,0.46)" stroke="#f5f5f5" stroke-width="1"/>
<!-- One landmark, one hairline: the week the end-to-end suite becomes the
largest pipeline. Nothing else is annotated -- the shape is the story. -->
<line x1="640" y1="52" x2="640" y2="92" stroke="rgba(45,49,66,0.35)" stroke-width="1"/>
<text x="640" y="44" fill="#4f5d75" font-size="7" font-family="'Geist Mono', monospace" letter-spacing="0.14em" text-anchor="middle">END-TO-END PASSES UNIT</text>
<text data-period="W01" data-index="0" x="80" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W01</text>
<text data-period="W02" data-index="1" x="160" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W02</text>
<text data-period="W03" data-index="2" x="240" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W03</text>
<text data-period="W04" data-index="3" x="320" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W04</text>
<text data-period="W05" data-index="4" x="400" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W05</text>
<text data-period="W06" data-index="5" x="480" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W06</text>
<text data-period="W07" data-index="6" x="560" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W07</text>
<text data-period="W08" data-index="7" x="640" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W08</text>
<text data-period="W09" data-index="8" x="720" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W09</text>
<text data-period="W10" data-index="9" x="800" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W10</text>
<text data-period="W11" data-index="10" x="880" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W11</text>
<text data-period="W12" data-index="11" x="960" y="440" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.08em" text-anchor="middle">W12</text>
<!-- Legend -->
<line x1="40" y1="462" x2="960" y2="462" stroke="rgba(45,49,66,0.10)" stroke-width="0.8"/>
<text x="40" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.18em">LEGEND</text>
<text x="960" y="478" fill="#4f5d75" font-size="8" font-family="'Geist Mono', monospace" letter-spacing="0.06em" text-anchor="end">BUILD MINUTES PER WEEK BY PIPELINE · WEEKLY BUCKETS · SYMMETRIC BASELINE, ENVELOPE HEIGHT = WEEKLY TOTAL · ILLUSTRATIVE FIGURES, NOT A MEASURED PROJECT</text>
<rect x="40" y="488" width="16" height="8" fill="#eb6c36"/>
<text data-layer="End-to-end" data-total="688" x="64" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">End-to-end · 688 min · focal</text>
<rect x="264" y="488" width="16" height="8" fill="rgba(45,49,66,0.78)"/>
<text data-layer="Unit" data-total="789" x="288" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Unit · 789 min</text>
<rect x="424" y="488" width="16" height="8" fill="rgba(45,49,66,0.62)"/>
<text data-layer="Integration" data-total="572" x="448" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Integration · 572 min</text>
<rect x="624" y="488" width="16" height="8" fill="rgba(45,49,66,0.46)"/>
<text data-layer="Lint" data-total="163" x="648" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Lint · 163 min</text>
<rect x="784" y="488" width="16" height="8" fill="rgba(45,49,66,0.30)"/>
<text data-layer="Docs" data-total="76" x="808" y="496" fill="#4f5d75" font-size="8.5" font-family="'Geist', sans-serif">Docs · 76 min · paused</text>
</svg>
</div>
<div class="cards">
<div class="card">
<p class="eyebrow">FOCAL · END-TO-END</p>
<div class="card-header"><span class="card-dot coral"></span><h3>The end-to-end suite is the growth</h3></div>
<p>12 minutes a week in week one, 118 in week twelve — it passes the unit suite in week eight and ends as the largest pipeline. Every other layer is roughly flat, so the widening of the whole stream is this one band widening.</p>
</div>
<div class="card">
<div class="card-header"><span class="card-dot ink-strong"></span><h3>The envelope is the total, by construction</h3></div>
<p>Each week is centred on one midline, top and bottom of the stream averaging back to it, so the stream's height is the week's total at 1.25px per minute. The smoothing bends only the drawing between weeks — every vertex sits on its true stacked value, which is what the verifier checks.</p>
</div>
<div class="card">
<div class="card-header"><span class="card-dot ink-light"></span><h3>The docs pinch is data, not a defect</h3></div>
<p>The docs pipeline ran nothing in weeks five and six, so its band pinches to zero thickness and reopens. The zero weeks stay in the domain — resampling or bridging them would erase the pause the layer exists to show.</p>
</div>
</div>
<div class="footer">
<span>build minutes per week by pipeline · weekly buckets · illustrative</span>
<span>example · diagram design</span>
</div>
</div>
</body>
</html>