Back to index

Kanban WIP

Three Column
Generates documents
Kanban WIP

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

kanban-wip-everything.md

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

Prompt

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

## What this layout does
Kanban WIP 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? — 出典・時点・母集団や仮定
- `columns` — array (id, label, limit) — 工程ID・名称・WIP上限
- `items` — array (id, label, columnId, owner, blocked, note) — 作業・所属工程・担当・ブロック状態

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 kanban-wip <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/kanban-wip/source/template.tsx
- https://design-vault.pages.dev/designs/kanban-wip/source/schema.ts
- https://design-vault.pages.dev/designs/kanban-wip/source/sample-data.json
- https://design-vault.pages.dev/designs/kanban-wip/sample.html

Files

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

template.tsx

6.8 KB

/designs/kanban-wip/source/template.tsx

Show contents
import type { ReactNode } from "react";
import type { Props } from "./schema";
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(var(--column-count,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-kanban-wip"><style>{CSS}</style><header><p className="eyebrow">KANBAN WIP</p><h1>{d.title}</h1>{d.subtitle && <p className="subtitle">{d.subtitle}</p>}</header>
<div className="horizontal-list" style={{"--column-count":d.columns.length} as import("react").CSSProperties}>{d.columns.map(c=>{const items=d.items.filter(r=>r.columnId===c.id);return <article key={c.id} className="board-column"><div className="flex"><h2>{c.label}</h2><span className={items.length>c.limit?"pill focus":"pill"}>{items.length} / 上限{c.limit}</span></div>{items.length>c.limit&&<p className="small">WIP超過:{items.length-c.limit}件</p>}{items.map(r=><div className="board-item" key={r.id}><h3>{r.id} · {r.label}</h3><p>{r.note}</p><p className="meta-line">{r.owner} {r.blocked?"/ ブロック中":"/ 進行可能"}</p></div>)}</article>})}</div><Notes><p>上限超過を表示するため、入力自体は拒否しません。工程移動は元データを更新して再生成します。</p></Notes>
<footer>閲覧用ボード。WIP超過とブロック状態を文字でも区別する。</footer></section>;
}

schema.ts

1009 B

/designs/kanban-wip/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 unique = (values: string[]) => new Set(values).size === values.length;

export const schema = z.object({
  title: text.describe("結論を伝える見出し"),
  subtitle: text.optional().describe("出典・時点・母集団や仮定"),
  columns:z.array(z.object({id:short,label:short,limit:z.number().int().positive()}).strict()).min(3).max(4).describe("工程ID・名称・WIP上限"),
  items:z.array(z.object({id:short,label:short,columnId:short,owner:short,blocked:z.boolean(),note:text}).strict()).min(1).max(12).describe("作業・所属工程・担当・ブロック状態"),
}).strict().refine(d=>unique(d.columns.map(c=>c.id))&&unique(d.items.map(r=>r.id))&&d.items.every(r=>d.columns.some(c=>c.id===r.columnId)),"工程と作業のID・参照を確認");
export type Props = z.infer<typeof schema>;

meta.yaml

1.3 KB

/designs/kanban-wip/source/meta.yaml

Show contents
id: kanban-wip
name:
  ja: WIP上限付きカンバン
  en: Kanban WIP
purpose:
  ja: 工程ごとの作業件数と仕掛かり上限を照合し、滞留を見つける。
  en: Kanban WIP with explicit source values and reading conditions.
status: published
still: ./still.png
category: operations
catalogOrder: 530
jobs:
  - prioritize-diagnose
useCases:
  - process
layoutKinds:
  - 3-column
tags:
  - kanban-wip
  - operations
  - offline
  - responsive
whenToUse:
  - ja: 工程ごとの作業件数と仕掛かり上限を照合し、滞留を見つける。
    en: Use to communicate kanban wip.
avoidWhen:
  - ja: 同じ条件・単位で比較できないデータには使わない
    en: Do not compare values with incompatible units, populations, or assumptions.
requiredInputs:
  - ja: 閲覧用ボード。WIP超過とブロック状態を文字でも区別する。
    en: Provide the source values, labels and decision conditions described by the schema.
relatedLayouts:
  - now-next-later
  - action-register
dataSchema:
  title: string — 結論を伝える見出し
  subtitle: string? — 出典・時点・母集団や仮定
  columns: array (id, label, limit) — 工程ID・名称・WIP上限
  items: array (id, label, columnId, owner, blocked, note) — 作業・所属工程・担当・ブロック状態
sources: []

sample-data.json

1.3 KB

/designs/kanban-wip/source/sample-data.json

Show contents
{
  "title": "レビュー工程のWIP超過を解消する",
  "subtitle": "架空サンプル|レイアウトの使用例",
  "columns": [
    {
      "id": "todo",
      "label": "準備",
      "limit": 5
    },
    {
      "id": "doing",
      "label": "作業中",
      "limit": 3
    },
    {
      "id": "review",
      "label": "レビュー",
      "limit": 2
    }
  ],
  "items": [
    {
      "id": "K1",
      "label": "テスト準備",
      "columnId": "todo",
      "owner": "検証担当",
      "blocked": false,
      "note": "観点を整理"
    },
    {
      "id": "K2",
      "label": "入力支援",
      "columnId": "doing",
      "owner": "開発担当",
      "blocked": false,
      "note": "実装を進行"
    },
    {
      "id": "K3",
      "label": "通知文面",
      "columnId": "review",
      "owner": "業務担当",
      "blocked": true,
      "note": "法務確認待ち"
    },
    {
      "id": "K4",
      "label": "権限一覧",
      "columnId": "review",
      "owner": "基盤担当",
      "blocked": false,
      "note": "差分確認"
    },
    {
      "id": "K5",
      "label": "画面文言",
      "columnId": "review",
      "owner": "UX担当",
      "blocked": false,
      "note": "表記を確認"
    }
  ]
}