Seen as a design The generated document itself — its toggles and disclosures work here
Seen as a design The generated document itself — its toggles and disclosures work here
Copy and paste, or save a file and hand it over. For a machine with no network, one “Everything” file is enough.
Everything in this panel, including the contents behind each URL, in one Markdown file — Large, so it goes as a file (copy is for 32 KB and under)
Build an HTML document with "Requirement Traceability Matrix". This layout runs, so there is no composition to rebuild — what it needs is content. ## What this layout does Link requirements to acceptance criteria, verification cases, and evidence to find untested scope. ## What you have to supply - Provide requirement IDs, acceptance criteria, owners, test IDs, methods, results, and evidence. ## The shape of the input - `title` — string — 追跡対象の結論 - `subtitle` — string? — 対象リリース・基準日・範囲 - `project` — string — 対象案件・リリース - `requirements` — array (3–10) — 優先度、受け入れ条件、責任者、検証ケースID - `tests` — array (2–16) — 検証方法、結果、証跡 schema.ts holds the machine-checkable validation. Ask rather than guess when an input is missing. ## When not to use it - Use fit-gap-assessment when the task is only to evaluate implementation fit. ## How to build it Put the input in a JSON file and run: ```bash node scripts/render.mjs requirement-traceability-matrix <input.json> --theme ink -o out.html ``` The output is one self-contained file and fetches nothing at render time. ## The source - https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/template.tsx - https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/schema.ts - https://design-vault.pages.dev/designs/requirement-traceability-matrix/source/sample-data.json - https://design-vault.pages.dev/designs/requirement-traceability-matrix/sample.html
Template, input schema, sample data and metadata. This document was generated from those four. The generated document
/designs/requirement-traceability-matrix/source/template.tsx
import type { Props } from "./schema";
const CSS = String.raw`
.asset-requirement-traceability-matrix{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2.5rem;line-height:1.6;font-size:var(--text-body-sm)}
.asset-requirement-traceability-matrix *{box-sizing:border-box}.asset-requirement-traceability-matrix header{margin-bottom:1.5rem}.asset-requirement-traceability-matrix .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary);margin-bottom:.75rem}.asset-requirement-traceability-matrix h1{font-size:var(--text-h1);line-height:1.3;max-width:28em}.asset-requirement-traceability-matrix .subtitle{margin-top:.75rem;color:var(--color-secondary)}
.asset-requirement-traceability-matrix .summary{display:flex;gap:2rem;flex-wrap:wrap;border-top:2px solid var(--color-primary);border-bottom:1px solid var(--color-outline);padding:.8rem 0;margin-bottom:1rem}.asset-requirement-traceability-matrix .summary p{margin:0;color:var(--color-secondary);font-size:var(--text-label)}.asset-requirement-traceability-matrix .summary strong{font-family:var(--font-mono);font-size:var(--text-h3);color:var(--color-primary);margin-right:.35rem}
.asset-requirement-traceability-matrix .requirements{border-top:1px solid var(--color-outline)}.asset-requirement-traceability-matrix .requirement{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.25fr);gap:1.4rem;padding:1.1rem 0;border-bottom:1px solid var(--color-outline);min-width:0}.asset-requirement-traceability-matrix .requirement>*{min-width:0}.asset-requirement-traceability-matrix .req-head{display:flex;gap:.65rem;align-items:baseline;flex-wrap:wrap}.asset-requirement-traceability-matrix .req-head code{font-family:var(--font-mono);font-size:var(--text-label);color:var(--color-tertiary)}.asset-requirement-traceability-matrix .req-head h2{font-size:var(--text-body-md);margin:0}.asset-requirement-traceability-matrix .priority{font-size:var(--text-label);color:var(--color-secondary);border:1px solid var(--color-outline);padding:.05rem .4rem}.asset-requirement-traceability-matrix .statement{margin:.5rem 0;font-weight:600;overflow-wrap:anywhere}.asset-requirement-traceability-matrix .acceptance{margin:0;color:var(--color-secondary);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .owner{margin-top:.45rem;font-size:var(--text-label);color:var(--color-secondary)}.asset-requirement-traceability-matrix .test-list{display:flex;flex-direction:column;gap:.45rem;margin:0;padding:0;list-style:none}.asset-requirement-traceability-matrix .test{display:grid;grid-template-columns:minmax(5.5rem,.7fr) minmax(0,1.5fr) auto;gap:.7rem;align-items:start;padding:.45rem .6rem;border-left:2px solid var(--color-tertiary);background:var(--color-surface-subtle);min-width:0}.asset-requirement-traceability-matrix .test-id{font-family:var(--font-mono);font-size:var(--text-label);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .test-name{font-weight:600;overflow-wrap:anywhere}.asset-requirement-traceability-matrix .test-meta{font-size:var(--text-label);color:var(--color-secondary);text-align:right;white-space:nowrap}.asset-requirement-traceability-matrix .evidence{grid-column:2/4;color:var(--color-secondary);font-size:var(--text-label);overflow-wrap:anywhere}.asset-requirement-traceability-matrix .empty-evidence{color:var(--color-tertiary);font-weight:600}.asset-requirement-traceability-matrix footer{margin-top:1.2rem;padding-top:.75rem;border-top:1px solid var(--color-outline);color:var(--color-secondary);font-size:var(--text-label)}.asset-requirement-traceability-matrix .still-svg{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
@media(max-width:680px){.asset-requirement-traceability-matrix{padding:1.5rem 1rem}.asset-requirement-traceability-matrix h1{font-size:var(--text-h2)}.asset-requirement-traceability-matrix .requirement{grid-template-columns:minmax(0,1fr);gap:.7rem;padding:1rem 0}.asset-requirement-traceability-matrix .test{grid-template-columns:minmax(0,1fr) auto;gap:.2rem .6rem}.asset-requirement-traceability-matrix .test-id{grid-column:1}.asset-requirement-traceability-matrix .test-name{grid-column:1}.asset-requirement-traceability-matrix .test-meta{grid-column:2;grid-row:1/3}.asset-requirement-traceability-matrix .evidence{grid-column:1/3}.asset-requirement-traceability-matrix .summary{gap:1rem}}
@media print{.asset-requirement-traceability-matrix{max-width:none;padding:1rem}.asset-requirement-traceability-matrix .requirement{break-inside:avoid}}
`;
const methods = { unit: "単体", integration: "結合", acceptance: "受入", manual: "手動" };
const results = { passed: "確認済み", failed: "不合格", pending: "未実施", blocked: "保留" };
const compact = (value: string, max: number) => [...value].length > max ? `${[...value].slice(0, max - 1).join("")}…` : value;
function Still({ data }: { data: Props }) {
const byId = new Map(data.tests.map((testCase) => [testCase.id, testCase]));
const rows = data.requirements.slice(0, 10);
const rowHeight = 51;
return <svg className="still-svg" data-layout-still="true" width="1280" height="720" viewBox="0 0 1280 720" aria-hidden="true">
<rect width="1280" height="720" fill="var(--color-surface)" />
<text x="48" y="48" fontSize="14" fontFamily="sans-serif" fill="var(--color-secondary)">{data.project}</text>
<text x="48" y="88" fontSize="28" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(data.title, 56)}</text>
<text x="48" y="117" fontSize="13" fontFamily="sans-serif" fill="var(--color-secondary)">要求から受け入れ検証までの対応を追跡</text>
<text x="48" y="145" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">要求</text>
<text x="560" y="145" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">紐づく検証ケース</text>
{rows.map((requirement, index) => {
const y = 174 + index * rowHeight;
return <g key={requirement.id}>
<line x1="48" x2="1232" y1={y - 16} y2={y - 16} stroke="var(--color-outline)" />
<text x="52" y={y + 5} fontSize="11" fontFamily="sans-serif" fill="var(--color-tertiary)">{requirement.priority}</text>
<text x="102" y={y + 5} fontSize="12" fontWeight="700" fontFamily="sans-serif" fill="var(--color-on-surface)">{requirement.id}</text>
<text x="190" y={y + 5} fontSize="12" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(requirement.statement, 48)}</text>
{requirement.testIds.slice(0, 4).map((id, testIndex) => {
const testCase = byId.get(id);
return <g key={id}>
<line x1={560 + testIndex * 164} x2={578 + testIndex * 164} y1={y} y2={y} stroke="var(--color-tertiary)" strokeWidth="2" />
<circle cx={560 + testIndex * 164} cy={y} r="4" fill="var(--color-tertiary)" />
<text x={584 + testIndex * 164} y={y + 4} fontSize="11" fontFamily="sans-serif" fill="var(--color-on-surface)">{compact(testCase?.id ?? id, 16)}</text>
</g>;
})}
</g>;
})}
<text x="48" y="704" fontSize="12" fontFamily="sans-serif" fill="var(--color-secondary)">{data.requirements.length}要件 · {data.tests.length}検証ケース</text>
</svg>;
}
export default function Template(data: Props) {
const byId = new Map(data.tests.map((testCase) => [testCase.id, testCase]));
const linkCount = data.requirements.reduce((count, requirement) => count + requirement.testIds.length, 0);
return <section className="asset asset-requirement-traceability-matrix">
<style>{CSS}</style>
<header>
<p className="eyebrow">{data.project} · 要件の検証状況</p>
<h1>{data.title}</h1>
{data.subtitle && <p className="subtitle">{data.subtitle}</p>}
</header>
<div className="summary" aria-label="追跡件数">
<p><strong>{data.requirements.length}</strong>要件</p>
<p><strong>{data.tests.length}</strong>検証ケース</p>
<p><strong>{linkCount}</strong>対応づけ</p>
</div>
<div className="requirements">
{data.requirements.map((requirement) => <article className="requirement" key={requirement.id}>
<div>
<div className="req-head"><code>{requirement.id}</code><span className="priority">{requirement.priority}</span><h2>{requirement.owner}</h2></div>
<p className="statement">{requirement.statement}</p>
<p className="acceptance"><strong>受け入れ条件:</strong> {requirement.acceptance}</p>
</div>
<ul className="test-list" aria-label={`${requirement.id}を検証するケース`}>
{requirement.testIds.map((id) => {
const testCase = byId.get(id)!;
return <li className="test" key={id}>
<code className="test-id">{testCase.id}</code>
<span className="test-name">{testCase.name}</span>
<span className="test-meta">{methods[testCase.method]} · {results[testCase.result]}</span>
<span className={testCase.evidence ? "evidence" : "evidence empty-evidence"}>{testCase.evidence ?? "証跡を記録"}</span>
</li>;
})}
</ul>
</article>)}
</div>
<footer>要求の完了は、対応づいた検証ケースと証跡を確認して判断する。</footer>
<Still data={data} />
</section>;
}
/designs/requirement-traceability-matrix/source/schema.ts
import { z } from "zod";
const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);
export const requirementSchema = z.object({
id: short.describe("要求の一意な識別子"),
priority: z.enum(["P0", "P1", "P2", "P3"]).describe("要求の優先度"),
statement: text.describe("実現する要求"),
acceptance: text.describe("満たしたと判断できる条件"),
owner: short.describe("要求の確認責任者"),
testIds: z.array(short).min(1).max(4).describe("要求を検証するケースID"),
}).strict();
export const verificationSchema = z.object({
id: short.describe("検証ケースの一意な識別子"),
name: short.describe("検証ケースの名称"),
method: z.enum(["unit", "integration", "acceptance", "manual"]).describe("検証方法"),
result: z.enum(["passed", "failed", "pending", "blocked"]).describe("実行結果"),
evidence: text.optional().describe("実行記録・証跡・未実施理由"),
}).strict();
export const schema = z.object({
title: text.describe("追跡対象の結論"),
subtitle: text.optional().describe("対象リリース・基準日・範囲"),
project: short.describe("対象案件・リリース"),
requirements: z.array(requirementSchema).min(3).max(10).describe("要求から検証ケースへの対応"),
tests: z.array(verificationSchema).min(2).max(16).describe("検証ケースと結果"),
}).strict().superRefine((data, ctx) => {
const requirementIds = data.requirements.map((item) => item.id);
const testIds = data.tests.map((item) => item.id);
if (new Set(requirementIds).size !== requirementIds.length) {
ctx.addIssue({ code: "custom", path: ["requirements"], message: "要求IDは一意にすること" });
}
if (new Set(testIds).size !== testIds.length) {
ctx.addIssue({ code: "custom", path: ["tests"], message: "検証ケースIDは一意にすること" });
}
const knownTests = new Set(testIds);
const usedTests = new Set<string>();
data.requirements.forEach((requirement, index) => {
if (new Set(requirement.testIds).size !== requirement.testIds.length) {
ctx.addIssue({ code: "custom", path: ["requirements", index, "testIds"], message: "同じ検証ケースを重複指定できない" });
}
requirement.testIds.forEach((id) => {
if (!knownTests.has(id)) {
ctx.addIssue({ code: "custom", path: ["requirements", index, "testIds"], message: `未登録の検証ケース: ${id}` });
}
usedTests.add(id);
});
});
data.tests.forEach((testCase, index) => {
if (!usedTests.has(testCase.id)) {
ctx.addIssue({ code: "custom", path: ["tests", index, "id"], message: `要求から参照されていない検証ケース: ${testCase.id}` });
}
});
});
export type Props = z.infer<typeof schema>;
/designs/requirement-traceability-matrix/source/meta.yaml
id: requirement-traceability-matrix
name:
ja: 要件トレーサビリティ表
en: Requirement Traceability Matrix
purpose:
ja: 要求・受け入れ条件・検証ケース・証跡を結び、未検証の範囲を追跡する。
en: Link requirements to acceptance criteria, verification cases, and evidence to find untested scope.
status: published
still: ./still.png
category: governance
catalogOrder: 800
jobs:
- trace-flow
- assign-governance
useCases:
- requirements
layoutKinds:
- table
tags:
- requirements
- verification
- traceability
- offline
- responsive
whenToUse:
- ja: 要求ごとに受け入れ条件と検証ケースを結び、実施結果と証跡を追うとき。
en: Use to connect each requirement to its acceptance criteria, verification cases, and evidence.
avoidWhen:
- ja: 実装可否や適合状況だけを評価する場合は、fit-gap-assessmentを使う。
en: Use fit-gap-assessment when the task is only to evaluate implementation fit.
requiredInputs:
- ja: 要件ID、受け入れ条件、責任者、検証ケースID・方法・結果・証跡を定義する。
en: Provide requirement IDs, acceptance criteria, owners, test IDs, methods, results, and evidence.
relatedLayouts:
- fit-gap-assessment
- release-readiness
dataSchema:
title: string — 追跡対象の結論
subtitle: string? — 対象リリース・基準日・範囲
project: string — 対象案件・リリース
requirements: array (3–10) — 優先度、受け入れ条件、責任者、検証ケースID
tests: array (2–16) — 検証方法、結果、証跡
sources: []
/designs/requirement-traceability-matrix/source/sample-data.json
{
"title": "受け入れ条件から実施証跡までを追跡する",
"subtitle": "架空の顧客管理刷新|2026年度 第2リリース",
"project": "顧客管理刷新",
"requirements": [
{
"id": "REQ-01",
"priority": "P0",
"statement": "利用者は登録済み顧客を検索できる",
"acceptance": "氏名または顧客番号で検索し、該当する顧客だけを表示する",
"owner": "プロダクト担当",
"testIds": [
"TEST-01",
"TEST-02"
]
},
{
"id": "REQ-02",
"priority": "P0",
"statement": "変更履歴を顧客詳細から確認できる",
"acceptance": "変更日時・変更者・変更項目が新しい順で表示される",
"owner": "業務担当",
"testIds": [
"TEST-03"
]
},
{
"id": "REQ-03",
"priority": "P1",
"statement": "権限のない利用者は顧客情報を変更できない",
"acceptance": "閲覧権限だけの利用者による更新要求を拒否し、記録する",
"owner": "セキュリティ担当",
"testIds": [
"TEST-04",
"TEST-05"
]
},
{
"id": "REQ-04",
"priority": "P2",
"statement": "顧客一覧をCSV形式で出力できる",
"acceptance": "画面で指定した絞り込みと列順を出力へ反映する",
"owner": "運用担当",
"testIds": [
"TEST-06"
]
}
],
"tests": [
{
"id": "TEST-01",
"name": "顧客番号で検索",
"method": "acceptance",
"result": "passed",
"evidence": "受入記録 AC-14。完全一致と該当なしを確認"
},
{
"id": "TEST-02",
"name": "氏名の一部で検索",
"method": "integration",
"result": "passed",
"evidence": "結合試験 INT-22。表記ゆれを含む3件を確認"
},
{
"id": "TEST-03",
"name": "変更履歴の並び順",
"method": "acceptance",
"result": "pending",
"evidence": "リリース候補で担当者レビューを予定"
},
{
"id": "TEST-04",
"name": "閲覧者の更新拒否",
"method": "integration",
"result": "passed",
"evidence": "権限試験 ACL-08。更新と監査記録を確認"
},
{
"id": "TEST-05",
"name": "監査ログの記録",
"method": "unit",
"result": "passed",
"evidence": "テスト結果 UT-91"
},
{
"id": "TEST-06",
"name": "絞り込みCSV出力",
"method": "manual",
"result": "blocked",
"evidence": "文字コード確認用の実データを準備中"
}
]
}