Back to index

Incident Review

Two Column
Generates documents
Incident Review

Seen as a design

Take it with you

Copy and paste, or save a file and hand it over. For a machine with no network, one “Everything” file is enough.

Everything

incident-review-everything.md

Everything in this panel, including the contents behind each URL, in one Markdown file

Prompt

1.6 KB
Build an HTML document with "Incident Review". This layout runs, so there is no composition to rebuild — what it needs is content.

## What this layout does
Incident Review with explicit source values and reading conditions.

## What you have to supply
- Provide the source values, labels and decision conditions described by the schema.

## The shape of the input
- `title` — string — 結論を伝える見出し
- `subtitle` — string? — 出典・時点・母集団や仮定
- `incident` — string — 事象ID
- `impact` — string — 影響範囲と期間
- `rootCause` — string — 確認済みの原因または未確定の明示
- `factors` — array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) — 寄与要因
- `events` — array (time, event) — 時刻を含む事実の時系列
- `actions` — array (task, owner, due) — 再発防止と担当・期限

schema.ts holds the machine-checkable validation. Ask rather than guess when an input is missing.

## When not to use it
- Do not compare values with incompatible units, populations, or assumptions.

## How to build it
Put the input in a JSON file and run:

```bash
node scripts/render.mjs incident-review <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/incident-review/source/template.tsx
- https://design-vault.pages.dev/designs/incident-review/source/schema.ts
- https://design-vault.pages.dev/designs/incident-review/source/sample-data.json
- https://design-vault.pages.dev/designs/incident-review/sample.html

Files

Template, input schema, sample data and metadata. This document was generated from those four. The generated document

template.tsx

6.9 KB

/designs/incident-review/source/template.tsx

Show contents
import type { ReactNode } from "react";
import type { Props } from "./schema";
function Ledger({heads,rows,caption}:{heads:string[];rows:ReactNode[][];caption?:string}) { return <table className="ledger">{caption && <caption>{caption}</caption>}<thead><tr>{heads.map((h,i)=><th key={i} scope="col">{h}</th>)}</tr></thead><tbody>{rows.map((r,i)=><tr key={i}>{r.map((v,j)=><td key={j} data-label={heads[j]}>{v}</td>)}</tr>)}</tbody></table>; }
function Notes({children}:{children:ReactNode}) { return <details><summary>データ・判断条件を確認</summary>{children}</details>; }

const CSS = String.raw`
.asset{font-family:var(--font-sans);color:var(--color-primary);background:var(--color-surface);max-width:var(--container-slide);margin:auto;padding:3rem 2rem;line-height:1.6;font-size:var(--text-body-sm)}
.asset *{box-sizing:border-box}.asset header{margin-bottom:2rem}.asset .eyebrow{font-family:var(--font-mono);font-size:var(--text-label);letter-spacing:.08em;color:var(--color-secondary);margin-bottom:.75rem}.asset h1{font-size:var(--text-h1);font-weight:700;line-height:1.35;letter-spacing:-.025em;max-width:32em}.asset h2{font-size:var(--text-h3);font-weight:600;margin-bottom:.75rem}.asset h3{font-size:var(--text-body-md);font-weight:600}.asset .subtitle,.asset .muted{color:var(--color-secondary)}.asset .subtitle{margin-top:.75rem}.asset .number{font-variant-numeric:tabular-nums;font-family:var(--font-mono)}.asset .hero-number{font-size:var(--text-display);line-height:1.2;font-weight:700;letter-spacing:-.04em}.asset .callout{border-left:.25rem solid var(--color-tertiary);padding:1rem 1.5rem;background:var(--color-surface-subtle);margin-bottom:1.5rem}.asset .grid2{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}.asset .grid3{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1.5rem}.asset .panel{padding:1.5rem;background:var(--color-surface-subtle);border-radius:var(--radius-md);min-width:0}.asset .rule{border-top:1px solid var(--color-outline);padding-top:1rem;margin-top:1rem}.asset .pill{display:inline-block;padding:.25rem .75rem;border:1px solid var(--color-outline);border-radius:var(--radius-full);font-size:var(--text-label)}.asset .focus{background:var(--color-tertiary-container);color:var(--color-on-tertiary-container)}.asset .dark{background:var(--color-primary);color:var(--color-on-primary)}.asset .dark .muted{color:var(--color-on-primary-container)}.asset .row{display:grid;grid-template-columns:12rem minmax(0,1fr) 6rem;gap:1rem;align-items:center;padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .row>*{min-width:0}.asset .track{height:1rem;position:relative;background:var(--color-neutral);border-radius:var(--radius-sm)}.asset .bar{height:100%;background:var(--color-tertiary);border-radius:var(--radius-sm)}.asset .track.tall{height:2rem}.asset .flex{display:flex;gap:1rem;align-items:center;justify-content:space-between;flex-wrap:wrap}.asset .stack>*+*{margin-top:1rem}.asset ul{list-style:disc;padding-left:1.25rem}.asset li+li{margin-top:.5rem}.asset .plot{display:block;width:100%;height:auto;overflow:visible}.asset .plot text{font-family:var(--font-sans);font-size:14px;fill:var(--color-secondary)}.asset .plot .ink{fill:var(--color-primary)}.asset .plot .axis{stroke:var(--color-outline);stroke-width:1}.asset .plot .accent{stroke:var(--color-tertiary);fill:none;stroke-width:3}.asset .legend{display:flex;gap:1.5rem;flex-wrap:wrap;font-size:var(--text-label);margin-bottom:1rem}.asset .legend b{display:inline-block;width:.75rem;height:.75rem;margin-right:.5rem}.asset .ledger{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums}.asset .ledger caption{text-align:left;font-weight:600;margin:1rem 0}.asset .ledger th{font-weight:600;color:var(--color-secondary);text-align:left}.asset .ledger th,.asset .ledger td{padding:.75rem 1rem;border-bottom:1px solid var(--color-outline);vertical-align:top;overflow-wrap:anywhere}.asset .ledger th:first-child,.asset .ledger td:first-child{padding-left:0}.asset .ledger th:last-child,.asset .ledger td:last-child{padding-right:0}.asset .ledger tbody tr:hover{background:var(--color-surface-subtle)}.asset details{border-top:1px solid var(--color-outline);margin-top:1.5rem;padding-top:1rem}.asset summary{cursor:pointer;color:var(--color-secondary);padding:.5rem 0;min-height:2.75rem}.asset summary:focus-visible{outline:2px solid var(--color-tertiary);outline-offset:.25rem}.asset .small{font-size:var(--text-label)}.asset .mobile-only{display:none}.asset .horizontal-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.asset .board-column{border-top:.25rem solid var(--color-primary);padding-top:1rem;min-width:0}.asset .board-item{padding:1rem 0;border-bottom:1px solid var(--color-outline)}.asset .board-item p{margin-top:.5rem}.asset .statement{font-size:var(--text-h2);line-height:1.5;font-weight:600}.asset .meta-line{margin-top:.75rem;font-size:var(--text-label);color:var(--color-secondary)}.asset pre{white-space:pre-wrap;overflow-wrap:anywhere}.asset footer{margin-top:2rem;font-size:var(--text-label);color:var(--color-secondary)}
@media(max-width:640px){.asset{padding:1.5rem 1rem}.asset h1{font-size:var(--text-h2)}.asset header{margin-bottom:1.5rem}.asset .grid2,.asset .grid3,.asset .horizontal-list{grid-template-columns:1fr}.asset .row{grid-template-columns:minmax(0,1fr) 5rem;gap:.5rem}.asset .row>:first-child{grid-column:1/-1}.asset .desktop-only{display:none!important}.asset .mobile-only{display:block}.asset .panel{padding:1rem}.asset .ledger,.asset .ledger tbody,.asset .ledger tr,.asset .ledger td{display:block}.asset .ledger thead{display:none}.asset .ledger tr{padding:.75rem 0;border-bottom:1px solid var(--color-outline)}.asset .ledger td{display:grid;grid-template-columns:7rem minmax(0,1fr);gap:.75rem;border:0;padding:.25rem 0!important}.asset .ledger td:before{content:attr(data-label);color:var(--color-secondary);font-size:var(--text-label)}.asset .hero-number{font-size:var(--text-h1)}}
@media print{.asset{max-width:none;padding:1rem}.asset details>*{display:block}.asset summary{display:none}}
`;

export default function Template(d: Props) {

return <section className="asset asset-incident-review"><style>{CSS}</style><header><p className="eyebrow">INCIDENT REVIEW</p><h1>{d.title}</h1>{d.subtitle && <p className="subtitle">{d.subtitle}</p>}</header>
<div className="callout"><strong>{d.incident} / 影響</strong><p>{d.impact}</p></div><div className="grid2"><article><h2>発生から復旧まで</h2>{d.events.map((e,i)=><div className="rule flex" key={i}><span className="number">{e.time}</span><p style={{flex:1}}>{e.event}</p></div>)}</article><article className="panel"><h2>原因と寄与要因</h2><p>{d.rootCause}</p><ul className="rule">{d.factors.map((s,i)=><li key={i}>{s}</li>)}</ul></article></div><Notes><Ledger heads={["再発防止","担当","期限"]} rows={d.actions.map(a=>[a.task,a.owner,a.due])}/></Notes>
<footer>確認済みの事実と原因仮説を区別する。</footer></section>;
}

schema.ts

1.1 KB

/designs/incident-review/source/schema.ts

Show contents
import { z } from "zod";
const text = z.string().trim().min(1).max(160);
const short = z.string().trim().min(1).max(48);
const value = z.number().finite();
const positive = value.positive();
const date = z.string().regex(/^\d{4}-\d{2}-\d{2}$/).refine(s => Number.isFinite(Date.parse(s)) && new Date(s).toISOString().slice(0,10) === s, "実在する日付を YYYY-MM-DD で指定する");

export const schema = z.object({
  title: text.describe("結論を伝える見出し"),
  subtitle: text.optional().describe("出典・時点・母集団や仮定"),
  incident:short.describe("事象ID"),impact:text.describe("影響範囲と期間"),rootCause:text.describe("確認済みの原因または未確定の明示"),factors:z.array(text).min(1).max(4).describe("寄与要因"),events:z.array(z.object({time:short,event:text}).strict()).min(3).max(6).describe("時刻を含む事実の時系列"),actions:z.array(z.object({task:text,owner:short,due:date}).strict()).min(1).max(4).describe("再発防止と担当・期限"),
}).strict();
export type Props = z.infer<typeof schema>;

meta.yaml

1.5 KB

/designs/incident-review/source/meta.yaml

Show contents
id: incident-review
name:
  ja: インシデントの事後レビュー
  en: Incident Review
purpose:
  ja: 影響・時系列・原因と寄与要因・再発防止を分けて記録する。
  en: Incident Review with explicit source values and reading conditions.
status: published
still: ./still.png
category: operations
catalogOrder: 600
jobs:
  - trace-flow
useCases:
  - case-study
layoutKinds:
  - 2-column
tags:
  - incident-review
  - operations
  - offline
  - responsive
whenToUse:
  - ja: 影響・時系列・原因と寄与要因・再発防止を分けて記録する。
    en: Use to communicate incident review.
avoidWhen:
  - ja: 同じ条件・単位で比較できないデータには使わない
    en: Do not compare values with incompatible units, populations, or assumptions.
requiredInputs:
  - ja: 確認済みの事実と原因仮説を区別する。
    en: Provide the source values, labels and decision conditions described by the schema.
relatedLayouts:
  - action-register
  - decision-record
dataSchema:
  title: string — 結論を伝える見出し
  subtitle: string? — 出典・時点・母集団や仮定
  incident: string — 事象ID
  impact: string — 影響範囲と期間
  rootCause: string — 確認済みの原因または未確定の明示
  factors: array (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) — 寄与要因
  events: array (time, event) — 時刻を含む事実の時系列
  actions: array (task, owner, due) — 再発防止と担当・期限
sources: []

sample-data.json

1.0 KB

/designs/incident-review/source/sample-data.json

Show contents
{
  "title": "連携停止の影響と、復旧までの判断を残す",
  "subtitle": "架空サンプル|レイアウトの使用例",
  "incident": "INC-042",
  "impact": "09:10〜09:42、申込通知が遅延。入力データの欠損なし。",
  "rootCause": "接続設定の更新が一部ノードに未適用",
  "factors": [
    "設定反映の確認項目が不十分",
    "通知遅延の監視が閾値を超えるまで検知しなかった"
  ],
  "events": [
    {
      "time": "09:10",
      "event": "通知の遅延が発生"
    },
    {
      "time": "09:18",
      "event": "監視で検知し調査開始"
    },
    {
      "time": "09:30",
      "event": "未反映ノードを特定"
    },
    {
      "time": "09:42",
      "event": "再反映後に配送正常化"
    }
  ],
  "actions": [
    {
      "task": "設定反映を自動照合",
      "owner": "基盤担当",
      "due": "2026-09-18"
    },
    {
      "task": "遅延監視の条件を見直す",
      "owner": "運用担当",
      "due": "2026-09-22"
    }
  ]
}