チュートリアル
このチュートリアルでは、短いトークを一本、ゼロから作ります。題材は「バグだと思ったら設計の問題だった」という 10 分のライトニングトーク。終わるころには、ビート付きの台本、喋りに合わせて要素が現れる手書きのスライド、2 枚をまたいで移動する図、ルールの内側で済ませたテーマ変更、そして USB メモリから発表できる HTML ファイルが手元にあります。
どの手順の終わりでも、そこで止めて発表できます。これは意図した設計です。dek は台本だけで足りるように作られていて、そのあとの手順はどれも、やってもやらなくてもよい仕上げです。
必要なのは Bun 1.4 以上だけ。声、動画、Playwright はここでは使いません。
1. プロジェクトを作る
bunx @hajimism/dek init lightning --deck postmortem
cd lightning
bun add -d @hajimism/dek
cd decks/postmortemこれは init が終わりに表示する手順そのものです。以降、dekc と書いたら bunx dekc、つまりプロジェクトに入れた dek のことです。
2. 台本を書く
script.md の中身をトークで置き換えます。声に出して言うとおりに書いてください。スライドのことはまだ考えません。
---
# yaml-language-server: $schema=../../.dek/schema.json
title: バグだと思ったら設計だった
event: Lightning Talks
date: 2026-05-14
duration: 10m
---
## intro
先月、見つけるのに 3 日かかったバグを出しました。
なぜ 3 日かかったのかを話します。理由はコードではなかったからです。
> ゆっくり。最初の一文を落ち着かせる。
## 何が見えたか {#symptom}
ある地域のユーザーだけ、古い価格が見えていました。間違った価格ではなく、古い価格。
毎回 5 分遅れで、そこだけ。
## どこを見たか {#search}
当然の場所を、当然の順番で見ました。
### キャッシュ {#cache}
まずキャッシュ。問題なし。
### キュー {#queue}
次にキュー。これも問題なし。
### 時計 {#clock}
そして、あるホストの時計。5 分遅れていました。
## なぜ 3 日かかったか {#cause}
時計がバグでした。でも 3 日かかった理由は、
どのホストがそのリクエストを処理したのかを、システムのどこも教えてくれなかったことです。
それはバグではありません。設計です。
## 何を変えたか {#fix}
時計を先に直しませんでした。すべてのレスポンスに「どこから来たか」を書かせました。
時計はそのあと 10 分で直りました。
## 持ち帰ってほしいこと {#close}
バグが見つからないとき、システムが何を言わないでいるかを問いましょう。いくつか見てほしいところがあります。
## introはそのまま id として通るので{#...}が要りません。他の見出しには日本語が入るので、それぞれ明示的な{#id}を付けています。id は HTML のファイル名になり、見出しの文言は観客と発表者ビューが見るものです。## どこを見たかの下の###が ビート です。喋りを 3 つの瞬間に区切ります。いまはまだ画面には何も起きません。それで構いません。- 引用は自分へのト書きです。声にならず、尺に数えられず、観客にも見えません。
3. 開発サーバを起動する
dekcURL が表示されるので開きます。スライドは 6 枚。どれも見出しから生成されたもので、1 枚目はデッキのタイトル、他は見出しの文言、search は 3 つのビートを列挙しています。
右矢印を押してください。search では 3 つのビートがひとつずつ現れてから、次の枚に進みます。p を押すと発表者ビューになり、いまのセクションの台本が表示され、いまのビートがハイライトされ、最初に送った瞬間から時計が動きます。
slides/search.html を見てみましょう。サーバがあなたの代わりに書いたものです。
<section class="slide" data-layout="default">
<h2 class="slide-title">どこを見たか</h2>
<ul>
<li data-step="cache">キャッシュ</li>
<li data-step="queue">キュー</li>
<li data-step="clock">時計</li>
</ul>
</section>各項目は data-step でビートを id で指しています。あるビートまで進むと、プレイヤーはそのビートとそれ以前に紐づく要素すべてを「表示済み」としてマークします。表示済みがどう見えるかはテーマが決めます。
ここで止めてもいい
dekc build を叩けば発表できるデッキがあります。ここから先は、台本に書いた以上のことを画面で伝えるための作業です。
4. 尺を確かめる
別の端末で、デッキの中から次を実行します。
dekc lsduration から来る予算、字数から来る見積もり、セクションごとの按分が出ます。dek は CJK を 300 字/分、それ以外を 130 語/分で見積もります。引用は数えません。このチュートリアルの台本は 1 分ほどで読み終わるので、dekc ls の最後に 1 diagnostic と出ます。中身は dekc lint や開発サーバが示す「10 分の予算に足りない」という DEK041 の警告です。それがこの警告の役目で、スライドではなく台本を書き足す合図です。エラーではなく警告なので、lint は通ります。声に出して言うつもりのことは、ファイルに書いてはじめて数に入ります。
5. 1 枚を手で書く
cause はこのトークでいちばん大事な枚です。ちゃんとしたレイアウトにしましょう。slides/cause.html を開いて骨格を置き換えます。
<section class="slide" data-layout="two-col">
<h2 class="slide-title">時計がバグで、沈黙が設計だった。</h2>
<div class="col">
<p class="node">cache</p>
<p class="node">queue</p>
<p class="node node-parent">clock: −5 min</p>
</div>
<div class="col">
<p class="node">response</p>
<p class="node node-parent">served-by: ???</p>
</div>
</section>保存します。ブラウザはこの 1 枚だけを差し替えます。使ったクラスはすべて同梱テーマにあるものです。slide-title、col、node、node-parent。試しにテーマにないクラス、たとえば class="node highlight" を足して保存してみてください。端末とブラウザのオーバーレイの両方が DEK010 を報告します。theme.css にそのクラスがない、と。消せば診断も消えます。
これがループです。書く、保存する、診断を読む、直す。開発サーバは保存のたびに lint を走らせ、「診断なし」は測れる問題が残っていないことを意味します。
6. 喋りに合わせて現す
cause にはまだビートがないので、両カラムが同時に出ます。「システムのどこも教えてくれなかった」と言う瞬間に右カラムが現れるよう、台本にビートを足します。
script.md の cause セクションを編集します。
## なぜ 3 日かかったか {#cause}
時計がバグでした。でも 3 日かかった理由は、別のところにあります。
### システムが知っていたこと {#knew}
キャッシュは問題なし、キューも問題なし、ひとつの時計が遅い。それは知っていました。
### システムが言わなかったこと {#silent}
でも、どのホストがそのリクエストを処理したのかは、どこも教えてくれなかった。
それはバグではありません。設計です。次に slides/cause.html でカラムをビートに結びます。
<section class="slide" data-layout="two-col">
<h2 class="slide-title">時計がバグで、沈黙が設計だった。</h2>
<div class="col" data-step="knew">
<p class="node">cache</p>
<p class="node">queue</p>
<p class="node node-parent">clock: −5 min</p>
</div>
<div class="col" data-step="silent">
<p class="node">response</p>
<p class="node node-parent">served-by: ???</p>
</div>
</section>この枚を送ってみてください。最初のビートで左カラム、次のビートで右カラムが現れます。いつ 出すかは台本が、どう 出すかはテーマが決めます。同梱テーマは要素をフェードしながら少し持ち上げて置き、display: none ではなく opacity で隠すので、ビートが進んでもレイアウトは動きません。
ビートは番号ではなく id で指しました。あとで途中にビートを挿しても、ここの対応はずれません。dekc sync の骨格も同じ id を使うので、HTML を書き始める前にビートへ {#id} を付けておく価値があります。
7. 図を次の枚へ連れていく
fix の枚では、cause の served-by のノードを引き継いで、答えに変えたい。両方の枚で同じ data-morph 名を要素に付けると、遷移の間にブラウザが位置とサイズを補間します。
slides/cause.html の最後のノードを変えます。
<p class="node node-parent" data-morph="served-by">served-by: ???</p>slides/fix.html を置き換えます。
<section class="slide" data-layout="default">
<h2 class="slide-title">すべてのレスポンスに出自を書かせる</h2>
<p class="node node-parent" data-morph="served-by">served-by: host-07</p>
<p class="node">そのあとで時計を直す。10 分。</p>
</section>cause から fix へ送ってください。served-by の箱がフェードアウトして再登場するのではなく、右カラムから新しい位置へ滑っていきます。アニメーションのコードは一行も書いていません。data-morph が view-transition-name になり、残りはブラウザの View Transitions API が処理します。
Playwright が入っていれば、遷移を途中で止めて動きを確かめられます。
dekc shot cause --to fix --at 0.58. ルールの内側で見た目を変える
既定のテーマは暗い配色です。このデッキは暖色にしましょう。デッキの中の theme.css(プロジェクト直下のものではありません)を開き、.slide ブロックのトークンを 3 つ変えます。
.slide {
--fg: #2b1d12;
--bg: #f6efe6;
--accent: #b5471f;
/* 他のトークンはそのまま */
}保存すると、全スライドが新しい配色で描き直されます。ファイルの他の場所は一切触っていません。テーマの中の色がすべて var(--fg)、var(--accent) のように書かれているからです。
今度はずるをしてみます。ルールに生の色を書いてください。
.slide .node-parent {
border-color: #ff0000;
}保存すると lint が DEK014 を報告します。トークンへの代入の外に生の値がある、と。生の色、フォント名、絶対単位は --* カスタムプロパティへの代入にだけ書けます。それ以外はすべて var() を通します。var(--accent) に戻せば、デッキは再びクリーンです。
このデッキのテーマは、このデッキだけのコピーです。プロジェクト直下の theme.css も、他のデッキも変わっていません。結果が気に入ったら、プロジェクト直下へ昇格させます。
cp theme.css ../../theme.css次の dekc new は暖色の配色から始まります。
9. 発表に使うファイルを作る
dekc builddist/postmortem.html ができます。全スライド、テーマ、プレイヤーのランタイム、画像がすべて minify された 1 ファイルにまとめられています。ブラウザで開いてください。矢印キーでビートとスライドを進めます。p で発表者ビュー。同じファイルを別ウィンドウでも開くと、2 つは BroadcastChannel で追従します。観客ビューをプロジェクタに、発表者ビューを手元のノート PC に。サーバもネットワークも要りません。
その 1 ファイルを USB メモリにコピーする。それがトークです。
この先
dek の中心になる機能をすべて使いました。順序と尺を持つ台本、1 スライド 1 HTML、id で結んだビート、枚をまたぐモーフ、トークンだけのテーマ、測れるものをすべて測る lint、単一ファイルのビルド。