mcpField
B
breadboarding

breadboarding

github.com/rjs開発1.4k179 回インストール5 週間前に更新
リポジトリ

breadboarding について

ワークフローの説明を、UI とコードのアフォーダンスおよび関係性のマップに変換する手法です。既存システムの把握と設計の具体化に対応し、既存部分と新規部分を組み合わせて扱えます。

主な機能

  1. 01ワークフローを UI とコードの表に整理
  2. 02アフォーダンス間の配線を記録
  3. 03プレイス、モード、サブプレイスをモデル化
  4. 04既存システムと設計変更を組み合わせ
  5. 05Mermaid による任意の可視化

活用シーン

  1. 01既存ワークフローの動作経路の把握
  2. 02提案設計の仕組みの具体化
  3. 03フロントエンドとバックエンドにまたがる変更の整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rjs/shaping-skills --skill breadboarding

インストール後、Claude Code で/breadboardingを入力して読み込まれたか確認します。

詳細

ブレッドボーディング

ブレッドボーディングは、ワークフローの説明を、アフォーダンスとその関係性を網羅したマップに変換します。出力は常に、番号付きの UI アフォーダンスとコードアフォーダンス、およびそれらの「出力先」と「戻り先」の関係を示す一連のテーブルです。テーブルが正確な情報源です。Mermaid の図は、人間向けの任意の視覚化です。


ユースケース

ブレッドボーディングには、2 つの用途があります。

1. 既存システムのマッピング

既存システムが具体的にどのように動作するのかを理解できていません。ある動作がどのように起きるのか、またはなぜ起きないのかを説明しようとしており、そのワークフローを理解する必要があります。

入力:

  • 分析対象のコードリポジトリ
  • ワークフローの説明(常に、UI を通じて、または呼び出し元として、効果を発生させようとするオペレーターの視点で記述)

出力:

  • UI アフォーダンステーブル
  • コードアフォーダンステーブル
  • (任意)Mermaid による視覚化

注: ワークフローが複数のアプリケーション(フロントエンド+バックエンド)にまたがる場合は、全体像を示すブレッドボードを 1 つ作成します。各システムが分かるように場所をラベル付けしてください。

2. 形作られたパーツからの設計

形作りによって、パーツ(メカニズム)の組み合わせとして新しいシステムをスケッチしました。具体的なメカニズムを詳しく示し、それらのパーツがシステムとしてどのように相互作用するかを示す必要があります。

入力:

  • パーツの一覧(形作りで定義したメカニズム)
  • パーツが達成する要件/成果(R)
  • 既存システム(任意)—新しいパーツが既存コードと相互運用する必要がある場合

出力:

  • UI アフォーダンステーブル
  • コードアフォーダンステーブル
  • (任意)Mermaid による視覚化
混在するケース

既存のまま維持する必要があるシステムに、形作りで定義した新しい要素や変更を加えるケースはよくあります。この場合、既存のアフォーダンスと新しいアフォーダンスをまとめてブレッドボードにし、それらのつながりを示します。

3. ホワイトボードのブレッドボードの読み方

手描きまたはホワイトボード上のブレッドボードでは、テーブルではなく、視覚的に積み重ねる形式を使用します。同じ概念(Place、アフォーダンス、ワイヤリング)が適用されますが、レイアウトの規則は異なります。

視覚的な規則:

要素表示方法
Place縦に積み重ねたブロックの最上部にある色付きブロック(多くの場合、ピンク/紫)
Place 内のアフォーダンスPlace のブロックの下に積み重ねられたブロック。スタック内の縦位置によって包含関係を示します
コードアフォーダンス通常、Place のスタック内ではなく、スタックの間に配置されます
Place ローダーPlace のブロックの左上に配置されたコードアフォーダンス。その Place を表示するために必要なデータ/入力を示します
Wires Outブロック間の実線の矢印
Returns Toブロック間の破線の矢印
条件分岐スタック内にインデントされたブロック。多くの場合、別の色(例:緑)で、if/else 分岐を示します
Place 参照スタック内の Place 名の前に付く _(_PlaceName の規則と同じ)
不確実/暫定アフォーダンス名の前に付く ? または ~、あるいは破線の枠。アフォーダンスが仮説段階であることを示します
包含ボックス複数のスタックを囲む大きな境界。システムまたは責任範囲ごとにアフォーダンスをまとめます(例:「HireEZ」ボックス)
注記/注釈要素の近くにある自由形式のテキスト。背景情報、未解決の質問、または根拠を示します

ホワイトボードのブレッドボードの読み方:

  1. Place を特定する — 各スタックの最上部にある色付きのヘッダーブロックを見つけます
  2. 各スタックを上から下へ読む — Place の下に積み重ねられたものはすべて、その Place に属します
  3. ローダーを見つける — Place のブロックの左上にあるコードアフォーダンスは、表示に必要なデータを示します
  4. ワイヤリングをたどる — スタック間の矢印をたどり、制御フロー(実線)とデータフロー(破線)を確認します
  5. 条件分岐に注意する — 色の異なるインデントされたブロックは、Place 内の分岐ロジックを示します
  6. 包含ボックスを確認する — 大きな境界は、システムまたは責任範囲の境界を示します
  7. 仮説段階の項目に印を付ける — ? や ~ が先頭に付いた項目は不確実であり、シェイピングの過程で残らない可能性があります

テーブルへの変換: ホワイトボードのブレッドボードを標準のアフォーダンステーブルに変換する際は、各スタックをその Place に対応付け、アフォーダンスを上から下へ列挙し、矢印を Wires Out/Returns To の関係として記録します。ローダーは、データを渡す UI アフォーダンスを Returns To の接続先とするコードアフォーダンスになります。


コア概念

Place

Place とは、相互作用の境界付きコンテキストです。Place にいる間は、次のようになります。

  • 利用できるアフォーダンスのセットが定められています
  • その境界の外にあるアフォーダンスとはやり取りできません
  • その場を離れるには、何らかの操作が必要です

Place は技術的なものではなく、知覚的なものです。 URL やコンポーネントについてではなく、ユーザーが現在のコンテキストとして体験するものを指します。Place は、今できることという観点での「現在いる場所」です。

ブロッキングテスト

あるものが別の Place かどうかを判断する最も簡単なテストは、**背後にあるものを操作できますか?**という問いです。

回答意味
いいえ別の Place にいます
はい同じ Place で、ローカルな状態が変化しています
例
UI 要素ブロッキングするかPlace か理由
モーダルはいはい背後のページを操作できません
確認ポップオーバーはいはい戻る前に応答する必要があります(モーダルの限界事例)
編集モード(画面全体が変化)はいはいすべてのアフォーダンスが変化します
チェックボックスで追加フィールドが表示されるいいえいいえ周囲は変化しません
ドロップダウンメニューいいえいいえほかの場所をクリックして閉じられ、操作を妨げません
ツールチップいいえいいえ情報を表示するもので、操作を妨げません
ローカル状態とプレイスナビゲーション

コントロールが状態を変更したとき、自問してください:すべてが変わったのか、それとも周囲はそのままで一部だけが変わったのか?

種類何が起こるかモデル化の方法
ローカル状態UI の一部が変化、周囲は変化なし同じプレイス、条件付き N → 依存する U
プレイスナビゲーション画面全体が変わる、またはブロッキングオーバーレイ異なるプレイス
モードベースのプレイス

モード(たとえば「編集モード」)が画面全体を変える場合 — ボタンがすべて変わり、あらゆるアフォーダンスが変わる — は、別々のプレイスとしてモデル化します:

PLACE: CMS Page (Read Mode)
PLACE: CMS Page (Edit Mode)

それらを切り替える状態フラグ(例:editMode$)はナビゲーションの仕組みであり、データストアではありません。どちらのプレイスにも S として含めないでください。

あらゆるコントロールに対する 3 つの質問

どんな UI アフォーダンスに対しても、自問してください:

  1. これを見るためにどこから来たのか?
  2. 今どこにいるのか?
  3. これに働きかけたらどこへ行くのか?

#3 の答えが「すべてが変わる」または「応答するまで背後にあるものと対話できない」なら、それは別のプレイスへのナビゲーションです。

ラベリングの慣例
パターン用途
PLACE: ページ名標準のページ/ルート
PLACE: ページ名 (モード)ページのモードベースのバリアント
PLACE: モーダル名モーダルダイアログ
PLACE: バックエンドAPI/データベースの境界

複数のシステムにまたがる場合は、システム名を付けてラベル付けします:PLACE: チェックアウトページ (フロントエンド)、PLACE: 決済 API (バックエンド)。

プレイス ID

プレイスはデータモデルにおける第一級の要素です。各プレイスには ID が付与されます:

#プレイス説明
P1CMS ページ(閲覧モード)閲覧専用の状態
P2CMS ページ(編集モード)ページレベルのコントロールを備えた編集状態
P2.1widget-grid (letters)サブプレイス:P2 内のレター編集ウィジェット
P3レターフォームモーダルレターを追加/編集するためのフォーム
P4バックエンドAPI リゾルバーとデータベース

プレイス ID によって以下が可能になります:

  • 明示的なナビゲーションの配線 — 内部のアフォーダンスではなく → P2 に配線する
  • 包含関係の追跡 — 各アフォーダンスがどのプレイスに属するかを宣言する
  • 一貫した Mermaid サブグラフ — サブグラフ ID がプレイス ID と一致する
プレイス参照

ネストされたプレイスが内部アフォーダンスを多く持ち、親を煩雑にする場合、それを切り離すことができます:

  1. 親プレイスに、アンダースコア接頭辞を使った参照ノードを置きます:_letter-browser
  2. 内部をすべて含む完全なプレイスを別途定義します
  3. 参照からプレイスへ配線します:_letter-browser --> letter-browser

参照はUI アフォーダンスです — 親のコンテキストにおいて「このウィジェット/コンポーネントがここにレンダリングされる」ことを表します。

flowchart TB
subgraph P1["P1: CMS Page (Read Mode)"]
    U1["U1: Edit button"]
    U_LB["_letter-browser"]
end

subgraph letterBrowser["letter-browser"]
    U10["U10: Search input"]
    U11["U11: Letter list"]
    N40["N40: performSearch()"]
end

U_LB --> letterBrowser

アフォーダンステーブルでは、参照を UI アフォーダンスとして記載します:

#アフォーダンスコントロール配線先
U1編集ボタンclick→ N1
_letter-browserウィジェット参照—→ P3

プレイス参照は、区別しやすいように破線の境界線でスタイル設定します:

classDef placeRef fill:#ffb6c1,stroke:#d87093,stroke-width:2px,stroke-dasharray:5 5
class U_LB placeRef
プレイスとしてのモード

コンポーネントに明確に異なるモード(閲覧 vs 編集、表示 vs 編集、折りたたみ vs 展開)がある場合、それらを別々のプレイスとしてモデル化します — ユーザーにとって異なる知覚状態だからです。

あるモードが別のモードのすべてを含み、さらに追加がある場合、拡張されたプレイス内にプレイス参照を表示してこれを示します:

P3: letter-browser (Read)    — base state
P4: letter-browser (Edit)    — contains _letter-browser (Read) + new affordances

参照は構成を示します:「P3 のすべてがここに現れ、さらにこれらの追加がある」。

flowchart TB
subgraph P3["P3: letter-browser (Read)"]
    U10["U10: Search input"]
    U11["U11: Letter list"]
end

subgraph P4["P4: letter-browser (Edit)"]
    U_P3["_letter-browser (Read)"]
    U3["U3: Add button"]
    U4["U4: Edit button"]
end

U_P3 --> P3

P4 のアフォーダンステーブルでは、参照は継承を示します:

#アフォーダンスコントロール配線先備考
_letter-browser (Read)P3 のすべてを継承—→ P3
U3追加ボタンclick→ N3NEW
U4編集ボタンclick→ N4NEW
サブプレイス

サブプレイスとは、プレイスの定義されたサブセットです。つまり、関連するアフォーダンスをグループ化する、プレイス内のまとまった領域です。サブプレイスは次のような場合に使用します:

  • 1 つのプレイスに複数の異なるウィジェットやセクションが含まれる場合
  • より大きなプレイスの一部分を詳細化する場合
  • スコープ内とスコープ外を区別して示したい場合

表記: 階層的な ID を使用します — P2 のサブプレイスには P2.1、P2.2 などと付けます。

| # | Place | Description |
|---|-------|-------------|
| P2 | Dashboard | Main dashboard page |
| P2.1 | Sales widget | Subplace: sales metrics |
| P2.2 | Activity feed | Subplace: recent activity |

アフォーダンスの表では、包含関係を示すためにサブプレイスの ID を使用します:

| U3 | P2.1 | sales-widget | "Refresh" button | click | → N4 | — |
| U7 | P2.2 | activity-feed | activity list | render | — | — |

Mermaid の場合: サブプレイスのサブグラフを親の内側にネストします。同じ背景色を使用してください(区別できる塗りつぶしは使わない)— サブプレイスは親の一部であり、独立したプレイスではありません:

flowchart TB
subgraph P2["P2: Dashboard"]
    subgraph P2_1["P2.1: Sales widget"]
        U3["U3: Refresh button"]
    end
    subgraph P2_2["P2.2: Activity feed"]
        U7["U7: activity list"]
    end
    otherContent[["... other dashboard content ..."]]
end

スコープ外コンテンツのプレースホルダー: あるサブプレイスを詳細化するときは、ページ上にさらに内容があることを示すために、兄弟となるプレースホルダーを追加します:

otherContent[["... other page content ..."]]

これは読者に「ここでは P2.1 を詳しく見ていますが、P2 にはまだ詳細化していない内容がほかにもあります」と伝えています。

包含と配線

これらはデータモデルにおける 2 つの異なる関係です:

関係意味記述する場所
包含(Containment)アフォーダンスがプレイスに属する / プレイス内に存在するPlace 列(集合の所属)
配線(Wiring)アフォーダンスが何かをトリガーする / 呼び出すWires Out 列(制御フロー)

包含は集合の所属です: U1 ∈ P1 は U1 がプレイス P1 の要素であることを意味します。すべてのアフォーダンスは正確に 1 つのプレイスに属します。

配線は制御フローです: U1 → N1 は U1 が N1 をトリガーすることを意味します。アフォーダンスは何にでも配線できます — 他のアフォーダンスでもプレイスでも構いません。

Place 列が答えるのは「このアフォーダンスはどこに存在するか?」です。 Wires Out 列が答えるのは「このアフォーダンスは何をトリガーするか?」です。

ナビゲーションの配線

アフォーダンスがナビゲーションを引き起こす場合(ユーザーがどこかへ「移動する」場合)、その内部のアフォーダンスではなく、プレイス自体に配線します:

✅ N1 Wires Out: → P2          (navigate to Edit Mode)
❌ N1 Wires Out: → U3          (wiring to affordance inside P2)

これにより、ナビゲーションが表の中で明示されます。プレイスが移動先であり、その内部の具体的なアフォーダンスは、そこに到達すると利用可能になります。

Mermaid では、これは次のようになります:

N1 --> P2

サブグラフの ID はプレイスの ID と一致するため、配線はプレイスの境界に接続されます。

アフォーダンス

操作の対象となるものです:

  • UI アフォーダンス(U): 入力欄、ボタン、表示される要素、スクロール領域
  • コードアフォーダンス(N): メソッド、サブスクリプション、データストア、フレームワークの機構
配線

アフォーダンス同士がどのように接続されるか:

Wires Out — アフォーダンスがトリガーまたは呼び出すもの(制御フロー):

  • 呼び出し配線: あるアフォーダンスが別のアフォーダンスを呼び出す
  • 書き込み配線: コードがデータストアに書き込む
  • ナビゲーション配線: 別のプレイスへルーティングする

Returns To — アフォーダンスの出力がどこへ流れるか(データフロー):

  • 戻り配線: 関数が呼び出し元に値を返す
  • 読み取り配線: データストアが別のアフォーダンスによって読み取られる

この分離により、データフローが明確になります。Wires Out は制御フロー(何が何をトリガーするか)を示します。Returns To はデータフロー(出力がどこへ行くか)を示します。


出力: アフォーダンスの表

表こそが真実です。すべてのブレッドボードは次の表を生成します:

Places 表
#PlaceDescription
P1Search PageMain search interface
P2Detail PageIndividual result view
UI アフォーダンス表
#PlaceComponentAffordanceControlWires OutReturns To
U1P1search-detailsearch inputtype→ N1—
U2P1search-detailloading spinnerrender——
U3P1search-detailresults listrender——
U4P1search-detailresult rowclick→ P2—
コードのアフォーダンス表
#場所コンポーネントアフォーダンス制御接続先戻り先
N1P1search-detailactiveQuery.next()呼び出し→ N2—
N2P1search-detailactiveQuery の購読監視→ N3—
N3P1search-detailperformSearch()呼び出し→ N4, → N5, → N6—
N4P1search.servicesearchOneCategory()呼び出し→ N7→ N3
N5P1search-detailloading書き込みストア→ U2
N6P1search-detailresults書き込みストア→ U3
N7P1typesense.servicerawSearch()呼び出し—→ N4
データストア表
#場所ストア説明
S1P1results検索結果の配列
S2P1loading読み込み状態を示すブール値
列の定義
列説明
#一意の ID(場所は P1、P2...、UI は U1、U2...、コードは N1、N2...、ストアは S1、S2...)
場所このアフォーダンスが属する場所(包含関係)
コンポーネントこのコンポーネント/サービスを所有するもの
アフォーダンス操作できる具体的なもの
制御トリガーとなるイベント:クリック、入力、呼び出し、監視、書き込み、レンダリング
接続先これによってトリガーされるもの:→ N4、→ P2(ナビゲーションを含む制御フロー)
戻り先出力の流れ先:→ N3 または → U2, U3(データフロー)

手順

既存システムをマッピングする場合

完全な実践例については、以下の 例 A を参照してください。

ステップ 1:分析するフローを特定する

具体的なユーザージャーニーを選びます。何かを実行しようとするオペレーターの視点で、必ず表現してください。

  • 「/search にアクセスし、クエリを入力し、さらに読み込むためにスクロールして、結果をクリックする」
  • 「カードトークンを指定して支払い API を呼び出し、請求が作成されることを期待する」

ステップ 2:関係するすべての場所を列挙する

ジャーニーをたどり、ユーザーが訪れる場所、またはシステム境界を越える箇所をそれぞれ特定します。

ステップ 3:コードをたどってコンポーネントを見つける

エントリーポイント(ルート、API エンドポイント)からコードをたどり、そのフローで関係するすべてのコンポーネントを見つけます。

ステップ 4:各コンポーネントのアフォーダンスを列挙する

コードを読み、以下を特定します。

  • UI:ユーザーに何が表示され、何を操作できますか?
  • コード:どのメソッド、購読、ストアが関係していますか?

ステップ 5:抽象概念ではなく、実際のものに名前を付ける

「DATABASE」と書いたら、いったん止めてください。実際のメソッドは何でしょうか?(userRepo.save())。すべてのアフォーダンス名は、コード内で実際に確認できるものでなければなりません。

ステップ 6:制御列を埋める

各アフォーダンスをトリガーするものは何ですか?(クリック、入力、呼び出し、監視、書き込み、レンダリング)

ステップ 7:接続先を埋める

各アフォーダンスがトリガーするものは何ですか?コードを読み、このメソッドが何を呼び出すのか、このボタンのハンドラーが何を実行するのかを確認します。

ステップ 8:戻り先を埋める

各アフォーダンスの出力はどこに流れますか?

  • 値を返す関数 → 戻り値を受け取る呼び出し元を列挙します
  • データストア → そのストアから読み取るアフォーダンスを列挙します
  • 意味のある出力がない場合 → — を使用します

ステップ 9:データストアをアフォーダンスとして追加する

コードがプロパティに書き込み、そのプロパティが後で別のアフォーダンスによって読み取られる場合、そのプロパティを制御タイプ write のコードアフォーダンスとして追加します。

ステップ 10:フレームワークの仕組みをアフォーダンスとして追加する

コードと UI のレンダリングをつなぐ cdr.detectChanges() などを含めます。これにより、状態の変更が実際に UI に反映される仕組みを示せます。

ステップ 11:コードと照合して確認する

コードをもう一度読みます。すべてのアフォーダンスが実在し、接続関係が実態と一致していることを確認します。


形状からパーツを設計する

スライスを含む完全な作業例については、以下の例 Bを参照してください。

ステップ 1:形状から各パーツを列挙する

形状設計で特定した仕組み/パーツをそれぞれ書き出します。

ステップ 2:パーツをアフォーダンスに変換する

各パーツについて、次の点を特定します。

  • このパーツにはどのような UI アフォーダンスが必要ですか?
  • このパーツを実装する Code アフォーダンスは何ですか?

ステップ 3:すべての U に対応する N があることを確認する

各 UI アフォーダンスについて、確認します。どの Code アフォーダンスがデータを提供するか、またはその描画を制御しますか?該当するものがなければ、不足している N を追加します。

ステップ 4:場所を既存または新規に分類する

各 UI アフォーダンスについて、次のどちらに属するかを判断します。

  • 変更する既存の場所
  • 作成する新しい場所

ステップ 5:アフォーダンスを接続する

各アフォーダンスの Wires Out と Returns To を記入します。想定する動作をたどり、何が何を呼び出し、何がどこに返されるかを確認します。

ステップ 6:既存システムに接続する(該当する場合)

既存のコードベースがある場合:

  • 新しいアフォーダンスが接続する必要のある既存のアフォーダンスを特定します
  • それらの既存のアフォーダンスをテーブルに追加します
  • 新しいアフォーダンスをそれらに接続します

ステップ 7:漏れがないことを確認する

  • すべての U に、データを供給する N があること
  • すべての N に、Wires Out または Returns To(あるいは両方)があること
  • ハンドラー → Wires Out があること
  • クエリ → Returns To があること
  • データストア → Returns To があること

ステップ 8:ユーザーに見える出力を U として扱う

ユーザーに表示されるもの(メールや通知など)はすべて UI アフォーダンスであり、それに接続する N が必要です。


重要な原則

記憶に頼らず、必ずデータを確認する

フローを逆方向にたどるときは、覚えている経路をたどらないでください。Wires Out 列を確認し、対象に接続するすべてのアフォーダンスを探します。

テーブルに記入するときは、各行を順を追って読みます。知っているつもりのことに頼らないでください。

テーブルが信頼できる唯一の情報源です。記憶は当てになりません。

マッピング時は、すべてのアフォーダンス名が実在する必要がある

既存のコードをマッピングするときに、抽象化を決して作り出さないでください。すべての名前は、コードベース内の実在するものを指している必要があります。

仕組みはアフォーダンスではない

アフォーダンスとは、システム内で意味のある識別性を持ち、作用を加えられるものです。アフォーダンスのように見えても、実際には単なる実装上の仕組みであるものがいくつかあります。

種類例アフォーダンスではない理由
視覚的なコンテナーmodal-frame wrapperwrapper に作用することはできません。単なる Place の境界です
内部変換letterDataTransform()呼び出し元の実装詳細であり、独立して作用できるものではありません
ナビゲーションの仕組みmodalService.open()Place に移動するための「方法」にすぎません。遷移先に直接接続します

これらは、最初の草稿では必ずしも明白ではありません。 アフォーダンスのテーブルを確認するときは、各 Code アフォーダンスを再確認し、次のように問いかけてください。

「これは本当にアフォーダンスですか。それとも、何かが起こる仕組みを詳しく説明しているだけですか?」

単なる「方法」であれば、それは省略し、遷移先または結果に直接接続します。

例:

❌ N8 --> N22 --> P3     (N22 is modalService.open — just mechanism)
✅ N8 --> P3             (handler navigates to modal)

❌ N6 --> N20 --> S2     (N20 is data transform — internal to N6)
✅ N6 --> S2             (callback writes to store)

❌ U7: modal-frame       (wrapper — just the boundary of P3)
✅ U8: Save button       (actionable)

ハンドラーは P3 に遷移します。コールバックはストアに書き込みます。モーダルが P3 です。仕組みは暗黙的です。

2 つのフロー:ナビゲーションとデータ

ブレッドボードでは、2 つの異なるフローを捉えます。

フロー追跡する内容接続
ナビゲーションPlace から Place への移動Wires Out → Places
データ状態がどのように表示に反映されるかReturns To → Us

この 2 つは直交しています。ナビゲーションがあってもデータが変わらない場合や、ナビゲーションがなくてもデータが変わる場合があります。

ブレッドボードを確認するときは、両方のフローをたどります。

  1. ナビゲーションフロー: Place から Place へ続くユーザーの操作の流れをたどれますか?
  2. データフロー: データを表示するすべての U について、そのデータの取得元をたどれますか?
データを表示するすべての U にはデータソースが必要です

データを表示する UI アフォーダンスには、データストア(S)か、データを返すコードアフォーダンス(N)のいずれかが必要です。

❌ U6: letter list (no incoming wire — where does the data come from?)
✅ S1 -.-> U6 (store feeds the display)
✅ N4 -.-> U6 (query result feeds the display)

表示する U にデータソースの配線が接続されていない場合、次のいずれかです。

  1. ブレッドボードにソースがありません
  2. その U は実在しません

ナビゲーションに集中していると、これは見落としがちです。必ず「この U はデータを表示しています。そのデータはどこから来るのでしょうか?」と確認してください。

すべての N は接続する必要があります

コードアフォーダンスに Wires Out も Returns To もない場合、何かが間違っています。

  • ハンドラー → Wires Out が必要です(呼び出す対象や書き込む先)
  • クエリ → Returns To が必要です(戻り値を受け取る先)
  • データストア → Returns To が必要です(読み取るアフォーダンス)
副作用にはストアが必要です

どこにも配線されていないように見える N は疑わしいものです。システムの境界外に副作用(ブラウザーの URL、localStorage、外部 API、分析機能)がある場合は、その外部状態を表すストアノードを追加してください。

❌ N41: updateUrl()           (wires to... nothing?)
✅ N41: updateUrl() → S15     (wires to Browser URL store)

これにより、データフローが明確になります。ストアには、外部状態がどのように戻ってくるかを示すリターンワイヤーを接続することもできます。

flowchart TB
N42["N42: performSearch()"] --> N41["N41: updateUrl()"]
N41 --> S15["S15: Browser URL (?q=)"]
S15 -.->|back button / init| N40["N40: activeQuery$"]

モデル化する一般的な外部ストア:

  • Browser URL — クエリパラメーター、ハッシュフラグメント
  • localStorage / sessionStorage — 永続化されるクライアント状態
  • Clipboard — コピー/貼り付け操作
  • Browser History — ナビゲーション状態
制御フローとデータフローを分ける

Wires Out = 制御フロー(何が何をトリガーするか) Returns To = データフロー(出力の送信先)

この分離によって、システムの動作が明確になります。

ナビゲーションはループではなく、インラインで示す

ルーティングは、すべてのページが使う汎用的な仕組みです。すべてのナビゲーションを中央の Router アフォーダンス経由で描くのではなく、発生する箇所に Router navigate() をインラインで示し、遷移先の Place に直接配線します。

ストアは書き込み元ではなく、動作を可能にする場所に配置する

データストアは、そのデータが何らかの効果を可能にするために消費されるPlace に属します。生成される場所ではありません。ほかの Place から書き込むことは、その Place の状態に「手を伸ばす」ことになります。

ストアの配置場所を決めるには:

  1. 読み取り/書き込みの関係をたどる — 誰が書き込みますか? 誰が読み取りますか?
  2. 配置場所は読み取り側で決まります — 動作を可能にする場所が配置先です
  3. 読み取る Place が 1 つだけの場合、ストアはその Place の中に配置します

例:changedPosts 配列は、Modal がユーザーによる変更の確定時に書き込みますが、PAGE_SAVE ハンドラーがユーザーによる Save のクリック時に読み取ります。このストアは PAGE_SAVE ハンドラーとともに配置します。そこで永続化処理を可能にするためです。

本当に共有される場合にのみ、共有領域に分離する

ストアを独立した DATA STORES セクションに置く前に、実際に複数の Place から読み取られていることを確認してください。1 つの Place での動作のみを可能にする場合は、その Place の中に配置します。

ストアは読み取るサブコンポーネントの中に配置する

Place 内では、動作を可能にするサブコンポーネントにストアを配置します。特定のハンドラーがストアを読み取る場合は、Place のレベルに浮かせるのではなく、そのハンドラーのコンポーネント内に配置します。

バックエンドは Place です

データベースやリゾルバーは、どこにも属さないインフラではありません。独自のアフォーダンスを持つ Place です。データベーステーブル(S)は、それらを読み書きするリゾルバー(N)とともに、バックエンド Place の中に配置します。


パーツと関係性のカタログ

このセクションでは、ブレッドボードに表示できるすべての要素を網羅的に説明します。

要素
要素ID パターン説明該当する条件
場所P1, P2, P3...インタラクションが行われる境界付きのコンテキストブロッキングテスト:背後にあるものとはインタラクションできない
サブ場所P2.1, P2.2...場所内に定義された部分集合より大きな場所内で、関連するアフォーダンスをまとめる
場所の参照_PlaceName切り離された場所を指す UI アフォーダンス複雑な入れ子の場所が別途定義されている
UI アフォーダンスU1, U2, U3...ユーザーが見たり、操作したりできるもの入力欄、ボタン、表示領域、スクロール領域
コードアフォーダンスN1, N2, N3...コード内で操作対象にできるものメソッド、サブスクリプション、ハンドラー、フレームワークの仕組み
データストアS1, S2, S3...保持され、読み書きされる状態データを保持するプロパティ、配列、オブザーバブル
チャンク—折りたたまれたサブシステム入力が 1 つ、出力が 1 つあり、内部要素が複数ある
プレースホルダー—対象範囲外のコンテンツを示すマーカー詳細を記述せずにコンテキストを示す
関係性
関係構文意味例
包含Place 列アフォーダンスが場所に属するPlace P2.1 内の U3
出力先→ X制御フロー:トリガーする/呼び出す→ N4、→ P2
戻り先→ X(「戻り先」列)データフロー:出力が送られる先→ U6、→ N3
省略フロー|label|中間のステップを省略S4 -.-> |view query| U6
親子関係階層 IDサブ場所が場所に属するP2.1 は P2 の子
包含と配線の違い
関係意味記録場所
包含アフォーダンスが場所に属する/場所内に存在するPlace 列(集合のメンバーシップ)
配線アフォーダンスが何かをトリガーする/呼び出すWires Out 列(制御フロー)

包含は集合のメンバーシップです:U1 ∈ P1 は、U1 が場所 P1 のメンバーであることを意味します。 配線は制御フローです:U1 → N1 は、U1 が N1 をトリガーすることを意味します。

各要素に該当する条件

場所(P):

  • ブロッキングテストに合格する — 背後にあるものとはインタラクションできない
  • 例:モーダル、編集モード(画面全体が変化する)、ルート/ページ
  • 該当しないもの:ドロップダウン、ツールチップ、フィールドを表示するチェックボックス

場所の参照(_PlaceName):

  • 切り離された場所を表す UI アフォーダンス
  • 入れ子の場所にアフォーダンスが多数あり、親が煩雑になる場合に使用する
  • 例:_letter-browser、_user-profile-widget
  • 場所全体の定義に接続する:_letter-browser --> P3

UI アフォーダンス(U):

  • ユーザーが見たり、操作したりできる
  • 例:ボタン、入力欄、リスト、スピナー、表示テキスト
  • 該当しないもの:ラッパー要素、レイアウトコンテナー

コードアフォーダンス(N):

  • 意味のある識別性があり、コード内で指し示せる
  • 例:handleSubmit()、query$ subscription、detectChanges()
  • 該当しないもの:内部変換、ナビゲーションの仕組み(以下を参照)

データストア(S):

  • 読み書きされる状態
  • 例:results 配列、loading ブール値、changedPosts リスト
  • 外部ストア:Browser URL、localStorage、Clipboard — アプリの境界外にある状態を表す
  • 該当しないもの:一度設定した後に変化しない設定(設定用アフォーダンスとして検討する)
検証チェック
チェック質問いいえの場合...
データを表示するすべての U入力ワイヤー(Wires Out または Returns To 経由)がありますか?データソースを追加する
すべての NWires Out または Returns To(あるいはその両方)がありますか?調査する — デッドコードか、配線漏れの可能性があります
すべての Sそこからデータを読み取るもの(Returns To)がありますか?調査する — 使用されていない可能性があります
ナビゲーションの仕組みこの N は、ある場所への移動方法にすぎませんか?代わりに場所へ直接配線する
副作用のある Nこの N は外部状態(URL、ストレージ、クリップボード)に影響しますか?外部状態用のストアを追加する

チャンク化

チャンク化すると、サブシステムがメイン図の単一ノードにまとめられ、詳細は別に表示されます。ブレッドボードの一部に次の特徴がある場合は、複雑さを管理するためにチャンク化を使用します。

  • 入力ワイヤーが1本(単一の入口)
  • 出力ワイヤーが1本(単一の出力)
  • その間に多数の内部要素がある
チャンク化するタイミング

配線をたどると、「ボトルネック」が見つかる箇所を探します。多くの操作要素が単一の入力と単一の出力に集約されている箇所です。こうした箇所は、チャンク化の自然な境界になります。

例:dynamic-form コンポーネントはフォーム定義を受け取り、多数のフィールド(U7a-U7k)を描画し、変更時に検証(N26)を行い、単一の valid$ シグナルを出力します。メイン図では、次のようになります。

N24 -->|formDefinition| dynamicForm
dynamicForm -.->|valid$| U8
チャンク化の方法
  1. メイン図で、サブシステムをスタジアム型の単一ノードに置き換えます。
dynamicForm[["CHUNK: dynamic-form"]]
  1. 境界シグナルを使って、チャンクへの配線とチャンクからの配線を行います。
N24 -->|formDefinition| dynamicForm
dynamicForm -.->|valid$| U8
  1. 境界マーカーを含む内部構造を示す、別のチャンク図を作成します。
flowchart TB
    input([formDefinition])
    output(["valid$"])

    subgraph chunk["dynamic-form internals"]
        N25["N25: generateFormConfig()"]
        U7a["U7a: field"]
        N26["N26: form value changes"]
        N27["N27: valid$ emission"]
    end

    input --> N25
    N25 --> U7a
    U7a --> N26
    N26 --> N27
    N27 --> output

    classDef boundary fill:#b3e5fc,stroke:#0288d1,stroke-dasharray:5 5
    class input,output boundary
  1. メイン図では、チャンクを独自のスタイルで表示します。
classDef chunk fill:#b3e5fc,stroke:#0288d1,color:#000,stroke-width:2px
class dynamicForm chunk
チャンクの色の規則
種類色Hex
チャンクノード(メイン図)ライトブルー#b3e5fc
境界マーカー(チャンク図)ライトブルー、破線#b3e5fc、stroke-dasharray:5 5
利点
  • メイン図の読みやすさを維持 — 複雑なサブシステムが単一のノードになります
  • 詳細を保持 — 必要に応じてチャンク図で内部構造を確認できます
  • 自然な境界 — チャンクは再利用可能なコンポーネントに対応することがよくあります

視覚化(Mermaid)

表が正です。Mermaid 図は、人が見るための任意の視覚化です。

基本構造
flowchart TB
    U1["U1: search input"] --> N1["N1: activeQuery.next()"]
    N1 --> N2["N2: subscription"]
    N2 --> N3["N3: performSearch"]
    N3 --> N4["N4: searchOneCategory"]
    N4 -.-> N3
    N3 --> N5["N5: loading store"]
    N3 --> N6["N6: results store"]
    N5 -.-> U2["U2: loading spinner"]
    N6 -.-> U3["U3: results list"]

    classDef ui fill:#ffb6c1,stroke:#d87093,color:#000
    classDef nonui fill:#d3d3d3,stroke:#808080,color:#000
    class U1,U2,U3 ui
    class N1,N2,N3,N4,N5,N6 nonui
線の規則
線のスタイルMermaid 構文用途
実線(-->)A --> B出力ワイヤー:呼び出し、トリガー、書き込み
破線(-.->)A -.-> B戻り先:戻り値、データストアの読み取り
ラベル付き ...`A -.->...
スコープ外のフローを省略する

データフローに、ブレッドボードのスコープに関係のない中間ステップがある場合は、ソースから宛先へ ... ラベルを付けて直接配線し、省略します。

S4 -.->|...| U6

これは「中間ステップを省略して、データがS4からU6に流れる」ことを示します。次の場合に使用します。

  • フローは存在するものの、その内部処理がスコープ外である
  • クエリーチェーンの詳細を示さずに、データの発生元を示す必要がある
  • ブレッドボードが1つのワークフロー(例:編集)に焦点を当てている一方で、別のワークフロー(例:表示)にも言及する必要がある
IDの接頭辞
接頭辞種類例
PプレイスP1, P2, P3
UUI操作要素U1, U2, U3
Nコード操作要素N1, N2, N3
SデータストアS1, S2, S3
色の規則
種類色Hex
プレイス(サブグラフ)白/透明—
UI操作要素ピンク#ffb6c1
コード操作要素グレー#d3d3d3
データストアラベンダー#e6e6fa
チャンクライトブルー#b3e5fc
プレイス参照ピンク、破線の境界線#ffb6c1
classDef ui fill:#ffb6c1,stroke:#d87093,color:#000
classDef nonui fill:#d3d3d3,stroke:#808080,color:#000
classDef store fill:#e6e6fa,stroke:#9370db,color:#000
classDef chunk fill:#b3e5fc,stroke:#0288d1,color:#000,stroke-width:2px
classDef placeRef fill:#ffb6c1,stroke:#d87093,stroke-width:2px,stroke-dasharray:5 5
サブグラフのラベルとプレイスID

ナビゲーションの配線が正しく接続されるよう、プレイスIDをサブグラフIDとして使用します。

flowchart TB
subgraph P1["P1: CMS Page (Read Mode)"]
    U1["U1: Edit button"]
    N1["N1: toggleEditMode()"]
end

subgraph P2["P2: CMS Page (Edit Mode)"]
    U2["U2: Save button"]
    U3["U3: Add button"]
end

%% Navigation wires to Place ID
N1 --> P2
種類IDのパターンラベルのパターン目的
プレイスP1, P2...P1: Page Nameユーザーが訪れる、境界のあるコンテキスト
トリガー—TRIGGER: Nameフローを開始するイベント(ナビゲーション不可)
コンポーネント—COMPONENT: Name複数のプレイスに登場する、再利用可能なUIとロジック
システム—SYSTEM: Name複数のアプリケーションにまたがる場合

重要な点: サブグラフID(P1、P2)は、プレイス表のプレイスIDと一致している必要があります。これにより、N1 --> P2 のようなナビゲーションワイヤーがプレイスの境界に接続されます。

複数のシステムにまたがる場合
flowchart TB
    subgraph frontend["SYSTEM: Frontend"]
        U1["U1: submit button"]
        N1["N1: handleSubmit()"]
    end

    subgraph backend["SYSTEM: Backend API"]
        N10["N10: POST /orders"]
        N11["N11: orderService.create()"]
    end

    U1 --> N1
    N1 --> N10
    N10 --> N11
ワークフローのステップ注釈(任意)

特定のワークフローをブレッドボード化する際、読者が流れを視覚的に追えるように、番号付きのステップマーカーを任意で追加できます。次のような場合に便利です。

  • 図が複雑で、ワークフローの流れが分かりにくい場合
  • 特定のユーザージャーニーを案内したい場合
  • ブレッドボードを手順説明や学習用ツールとして使う場合

形式:

図の前にワークフローガイドの表を追加します。

| Step | Action | Where to look |
|------|--------|---------------|
| **1** | Click "Edit" button | U1 → N1 → S1 |
| **2** | Edit mode activates | S1 → N2 → U3 |
| **3** | Click "Add" | U3 → N3 → N8 |

Mermaid 図に、スタジアム形のノードを使ってステップマーカーを追加します。

flowchart TB
    %% Step markers
    step1(["1 - CLICK EDIT"])
    step2(["2 - EDIT MODE ON"])
    step3(["3 - CLICK ADD"])

    %% Connect steps to relevant affordances with dashed lines
    step1 -.-> U1
    step2 -.-> N2
    step3 -.-> U3

    %% Style step markers green
    classDef step fill:#90EE90,stroke:#228B22,color:#000,font-weight:bold
    class step1,step2,step3 step

書式の注意点:

  • "1 - ACTION" 形式を使用します(番号、スペース、ハイフン、スペース、アクション)。
  • "1. ACTION" は避けてください。ピリオドによって Mermaid の Markdown リストパーサーが起動します。
  • "1) ACTION" は避けてください。括弧も解析の問題を引き起こすことがあります。
  • ステップマーカーは破線(-.->)でアフォーダンスに接続します。
  • ステップは緑色にして、UI(ピンク)および Code(グレー)のアフォーダンスと区別します。

ブレッドボードのスライス化

スライス化では、ブレッドボードのアフォーダンスをグループ化し、垂直の実装スライスに分けます。スライス化の完全な例については、以下の例 Bを参照してください。

入力:

  • ブレッドボード(配線付きのアフォーダンステーブル)
  • Shape(R + メカニズム)— どのデモが重要かを判断する指針

出力:

  • アフォーダンスを V1~V9 のスライスに割り当てたブレッドボード(最大 9 スライス)
垂直スライスとは?

垂直スライスとは、デモ可能な機能を実現する UI と Code のアフォーダンスのグループです。すべてのレイヤー(UI、ロジック、データ)を横断して、動作する増分を提供します。

対照的なのが水平スライスです。水平スライスでは、インターフェースからクリックできない作業(例:「すべてのデータモデルをセットアップする」)を、1 つのレイヤーで行います。

重要な制約

すべてのスライスに、デモできる可視の UI が必要です。 UI のないスライスは水平レイヤーであり、垂直スライスではありません。

  • ✅ 「セルフサービス署名パス」(デモ:チェックアウト → 署名 → 署名を確認)
  • ❌ 「データベーススキーマ」(デモができない)

デモ可能であるための条件:

  • エントリーポイント(UI 操作またはトリガー)がある
  • 観測可能な出力(UI の描画、または効果の発生)がある
  • R に向けて意味のある進捗を示す

何を「意味のある進捗」とするかは Shape が指針になります。アフォーダンスを恣意的にグループ化するのではなく、メカニズムが機能する様子を示すためにグループ化します。

スライスのサイズ
  • 小さすぎる: UI アフォーダンスが 1~2 個だけで、意味のあるデモができない → 関連するスライスと統合します
  • 大きすぎる: アフォーダンスが 15 個以上、または複数の無関係なユーザージャーニーが含まれる → 分割します
  • 適切なサイズ: 「これを実演します」と明確に示せる、一貫したユーザージャーニー

スライスは 9 個以下を目指してください。それ以上必要な場合は、1 サイクルで扱うには Shape が大きすぎる可能性があります。

将来のスライスへのワイヤー

スライスには、後続のスライスのアフォーダンスを指す Wires Out を持つアフォーダンスが含まれることがあります。これらのワイヤーはブレッドボード上に存在しますが、後続のスライスが実装されるまでは実装されておらず、スタブまたは何もしない処理となります。

これは通常のことです。ブレッドボードはシステム全体を示し、スライス化は実装順序を示します。

手順

ステップ 1:デモ可能な最小の増分を特定する

ブレッドボードと形状を確認します。「中核となる仕組みが動作することを示す、最小のサブセットは何か?」と考えてください。

通常、次のようなものです。

  • 中核となるデータ取得
  • 基本的なレンダリング
  • 検索、ページネーション、状態の永続化はまだ含めない

これを V1 とします。

ステップ 2:追加機能をスライスとして重ねていく

形状に含まれる仕組みを確認します。各スライスでは、仕組みが動作することを示します。

  • V2:検索入力(検索の仕組みを示す)
  • V3:ページネーション/無限スクロール(ページネーションの仕組みを示す)
  • V4:URL 状態の永続化(状態保持の仕組みを示す)
  • など

**スライスは最大 9 つです。**それ以上ある場合は、関連する仕組みをまとめてください。単独では意味をなさない機能は、同じスライスに含めてください。

ステップ 3:アフォーダンスをスライスに割り当てる

すべてのアフォーダンスを確認し、そのスライスの仕組みを初めてデモする際に必要となるスライスに割り当てます。

スライス仕組みアフォーダンス
V1コア表示U2, U3, N3, N4, N5, N6, N7
V2検索U1, N1, N2
V3ページネーションU10, N11, N12, N13

一部のアフォーダンスには、後続のスライスへのワイヤーアウトがある場合がありますが、問題ありません。割り当てられたスライスで実装し、ワイヤーがまだ何もしないだけです。

ステップ 4:スライスごとにアフォーダンスの表を作成する

各スライスについて、追加するアフォーダンスだけを取り出します。

V2:検索が動作する

#コンポーネントアフォーダンス操作ワイヤーアウト戻り先
U1search-detail検索入力入力→ N1—
N1search-detailactiveQuery.next()呼び出し→ N2—
N2search-detailactiveQuery のサブスクリプション監視→ N3—

ステップ 5:スライスごとにデモ文を作成する

各スライスには、R に向けてその仕組みが動作することを示す具体的なデモが必要です。

  • V1:「ウィジェットに API からの実データが表示される」
  • V2:「「dharma」と入力すると、結果がリアルタイムで絞り込まれる」
  • V3:「下にスクロールすると、さらに項目が読み込まれる」

デモは、進捗を示すものとして関係者に見せられる内容にしてください。

Mermaid でスライスを可視化する

各スライスの図にはブレッドボード全体を表示し、スタイルで対象範囲を区別してください。

カテゴリスタイル説明
このスライス明るい色追加するアフォーダンス
構築済みグレーの塗りつぶし以前のスライス
今後の予定透明、破線の枠線まだ構築していないもの
flowchart TB
    U1["U1: search input"]
    U2["U2: loading spinner"]
    N1["N1: activeQuery.next()"]
    N2["N2: subscription"]
    N3["N3: performSearch"]

    U1 --> N1
    N1 --> N2
    N2 --> N3
    N3 --> U2

    %% V2 scope (this slice) = green
    classDef thisSlice fill:#90EE90,stroke:#228B22,color:#000
    %% Already built (V1) = grey
    classDef built fill:#d3d3d3,stroke:#808080,color:#000
    %% Future = transparent dashed
    classDef future fill:none,stroke:#ddd,color:#bbb,stroke-dasharray:3 3

    class U1,N1,N2 thisSlice
    class U2,N3 built

これにより、関係者は次のことを確認できます。

  • 現在構築中のもの(強調表示)
  • すでに存在するもの(グレー)
  • 今後追加されるもの(薄く表示)
スライスの概要形式
#スライス仕組みデモ
V1実データを含むウィジェットF1, F4, F6「ウィジェットに API から文字が表示される」
V2検索が動作するF3「入力すると結果が絞り込まれる」
V3無限スクロールF5「下にスクロールすると、さらに読み込まれる」
V4URL 状態F2「更新しても検索状態が保持される」

「仕組み」列では、形状の各要素を参照し、各スライスでどの仕組みを示すのかを表しています。



例

例 A:既存システムのマッピング

この例では、複数のエントリーポイントを通じてデータがどのように流れるかを理解するために、既存システムをブレッドボーディングします。

入力

理解するワークフロー:「admin_organisation_countries はどのように変更され、その後どのように読み取られるのか?複数のエントリーポイントがあります。手動編集、チェックボックスの切り替え、バッチジョブです。」

出力

UI のアフォーダンス

#コンポーネントアフォーダンス操作接続先戻り先
U1SSO Adminrole_profiles のチェックボックス描画——
U2SSO Admin「Country Admin」チェックボックスクリック選択状態を切り替え—
U3SSO Adminadmin_countries の filter_horizontal描画——
U4SSO Admin利用可能な国のリスト描画——
U5SSO Admin選択済みの国のリスト描画——
U6SSO Admin追加 →/削除 ←クリック選択内容を変更—
U7SSO Admin保存ボタンクリック→ N3—
U20DWConnect「Country admins」セクション描画——
U21(不明)システムメールの「From」フィールド描画——

コードのアフォーダンス

#コンポーネントアフォーダンス操作接続先戻り先
N1sso/accounts/adminget_fieldsets()呼び出し→ U3(条件付き)—
N2sso/accounts/modelsget_administrable_user_countries()呼び出し—→ U4
N3sso/accounts/adminsave_form()呼び出し→ N4、→ N5—
N4Django Adminフォームの M2M 保存呼び出し→ S2—
N5sso/forms/mixins_update_user_m2m()呼び出し→ S1、→ N6—
N6sso/signalsuser_m2m_field_updated シグナルシグナル→ N10—
N7CLI/Schedulermanage.py dwbn_cleanup実行→ N15—
N10sso-dwbn-themedwbn_user_m2m_field_updated()受信→ N11—
N11sso-dwbn-themedwbn_user_m2m_field_updated_task()呼び出し→ N12—
N12sso-dwbn-themeCountry Admin が追加され、かつ管理対象の国がゼロか?条件分岐→ N20—
N15sso-dwbn-themeadmin_changes()呼び出し→ N16—
N16sso-dwbn-theme各 Country Admin について:ホームセンターの国が未設定か?ループ→ N20—
N20sso-dwbn-themeホームセンターの国を取得呼び出し→ N21—
N21sso-dwbn-themeadmin_organisation_countries.add()呼び出し→ S2—
N22sso-dwbn-themeupdate_last_modified()呼び出し——
N30dwconnect2-backendfindCenterAdmins()呼び出し—→ U20
N31sso/apiget_object_data()呼び出し—→ 外部

データストア

#ストア説明
S1role_profilesM2M:ユーザーが持つロールプロファイル
S2admin_organisation_countriesM2M:ユーザーが管理する国
S3organisationsユーザーのホームセンター

Mermaid 図

flowchart TB
    subgraph stores["DATA STORES"]
        S1["S1: role_profiles"]
        S2["S2: admin_organisation_countries"]
        S3["S3: organisations"]
    end

    subgraph ssoAdmin["PLACE: SSO Admin — User Change Page"]
        subgraph permissions["Permissions fieldset"]
            U1["U1: role_profiles checkboxes"]
            U2["U2: 'Country Admin' checkbox"]
        end

        subgraph userAdmin["User admin fieldset (superuser only)"]
            U3["U3: admin_countries filter_horizontal"]
            U4["U4: Available countries"]
            U5["U5: Selected countries"]
            U6["U6: Add → / Remove ←"]
        end

        U7["U7: Save button"]
        N1["N1: get_fieldsets()"]
        N2["N2: get_administrable_user_countries()"]
        N3["N3: save_form()"]
        N4["N4: Form M2M save"]
        N5["N5: _update_user_m2m()"]
        N6["N6: user_m2m_field_updated signal"]

        N1 -->|is_superuser| userAdmin
        U3 --> U4
        U3 --> U5
        U6 --> U5
        N2 -.-> U4

        U2 --> U7
        U6 --> U7
        U7 --> N3
        N3 --> N4
        N3 --> N5
        N5 --> N6
    end

    subgraph trigger["TRIGGER: Batch Cleanup"]
        N7["N7: manage.py dwbn_cleanup"]
    end

    subgraph theme["sso-dwbn-theme"]
        N10["N10: dwbn_user_m2m_field_updated()"]
        N11["N11: dwbn_user_m2m_field_updated_task()"]
        N12["N12: Country Admin added AND zero admin countries?"]
        N15["N15: admin_changes()"]
        N16["N16: For each Country Admin: home center country missing?"]
        N20["N20: Get home center's country"]
        N21["N21: admin_organisation_countries.add()"]
        N22["N22: update_last_modified()"]

        N6 --> N10
        N10 --> N11
        N11 --> N12
        N7 --> N15
        N15 --> N16
        N12 -->|yes| N20
        N16 -->|yes| N20
        N20 --> N21
        N21 --> N22
    end

    subgraph dwconnect["PLACE: DWConnect — Center Page"]
        N30["N30: findCenterAdmins()"]
        U20["U20: 'Country admins' section"]

        N30 --> U20
    end

    subgraph api["TRIGGER: External API Request"]
        N31["N31: get_object_data()"]
    end

    U21["U21: System email 'From' field"]

    N4 --> S2
    N5 --> S1
    N21 --> S2
    S1 -.-> N15
    S3 -.-> N16
    S3 -.-> N20
    S2 -.-> U5
    S2 -.-> N30
    S2 -.-> N31
    S2 -.-> U21

    classDef ui fill:#ffb6c1,stroke:#d87093,color:#000
    classDef nonui fill:#d3d3d3,stroke:#808080,color:#000
    classDef store fill:#e6e6fa,stroke:#9370db,color:#000
    classDef condition fill:#fffacd,stroke:#daa520,color:#000
    classDef trigger fill:#98fb98,stroke:#228b22,color:#000

    class U1,U2,U3,U4,U5,U6,U7,U20,U21 ui
    class N1,N2,N3,N4,N5,N6,N10,N11,N15,N20,N21,N22,N30,N31 nonui
    class N12,N16 condition
    class N7 trigger
    class S1,S2,S3 store

例 B:形状のある部品から設計する


Part 1:コンテキストの形成(ブレッドボーディングへの入力)

このセクションでは、形成から得られるもの、つまり要件、特定された既存のパターン、スケッチしたパーツを示します。これがブレッドボーディングに渡される入力です。

注: この例では、シェーピングの用語を使用しています。シェーピングでは、要件(Rs)を定義し、再利用する既存のパターンを特定して、パーツやメカニズムとしてソリューションをスケッチします。ブレッドボーディングでは、このように形成されたソリューションを引き継ぎ、具体的なアフォーダンスと接続を詳しく記述します。

R(要件)

ID要件
R0インデックスページからコンテンツを検索できるようにする
R2詳細ページから戻ったときに、ページネーションの状態に戻る
R3詳細ページから戻ったときに、検索の状態に戻る
R4検索/ページネーションの状態がページの再読み込み後も保持される
R5ブラウザーの戻るボタンで、以前の検索/ページネーションの状態を復元する
R9検索入力をデバウンスする(キー入力のたびに検索を実行しない)
R10検索には最低3文字を必要とする
R11読み込み中および空の状態で、ユーザーにフィードバックを提供する

再利用可能なパターンを備えた既存システム(S-CUR)

アプリには、これらのRsの大半を実装するグローバル検索ページがすでにあります。シェーピングでは、パーツ/メカニズムのレベルで次のように記録されています。

パーツメカニズム
S-CUR1URL の状態と初期化
S-CUR1.1Router の queryParams observable が {q, category} を提供する
S-CUR1.2initializeState(params) が URL からクエリとカテゴリを設定する
S-CUR1.3ページの読み込み時に、URL の状態から初期検索を実行する
S-CUR2検索入力
S-CUR2.1検索入力が activeQuery BehaviorSubject にバインドされる
S-CUR2.2activeQuery のサブスクリプションで90msのデバウンスを行う
S-CUR2.33文字以上で performNewSearch() を実行する
S-CUR3データ取得
S-CUR3.1performNewSearch() が読み込み中の状態を設定し、検索サービスを呼び出す
S-CUR3.2検索サービスが Typesense フィルターを作成し、rawSearch() を呼び出す
S-CUR3.3rawSearch() が Typesense にクエリを送り、{found, hits} を返す
S-CUR3.4結果が detailResult データストアに書き込まれる
S-CUR4ページネーション
S-CUR4.1一番下までスクロールすると、intercomService 経由で appendNextPage() が実行される
S-CUR4.2appendNextPage() がページを進め、検索を呼び出す
S-CUR4.3新しいヒットが既存のヒットに連結される
S-CUR4.4sendMessage() がスクロール検出を再設定する
S-CUR5レンダリング
S-CUR5.1cdr.detectChanges() がテンプレートの再評価を実行する
S-CUR5.2読み込み中のスピナー、「結果なし」、結果数をストアに基づいて表示する
S-CUR5.3*ngFor が各ヒットのタイルをレンダリングする
S-CUR5.4タイルをクリックすると詳細ページに移動する

スケッチしたソリューション:S-CURを応用するパーツ

新しいソリューションのパーツでは、応用するS-CURのパターンを明示的に参照します。

パーツメカニズム応用元
F1ウィジェットを作成する(コンポーネント、定義、登録)—
F2URL の状態と初期化(?q= を読み取り、読み込み時に復元)S-CUR1
F3検索入力(デバウンス、最低3文字、検索を実行)S-CUR2
F4データ取得(フィルター付きの rawSearch())S-CUR3
F5ページネーション(一番下までスクロール、ページを追加、再設定)S-CUR4
F6レンダリング(読み込み中、空の状態、検索結果リスト、行)S-CUR5

パート 2:ブレッドボーディング(パーツ → アフォーダンスへの変換)

ここでブレッドボーディングを行います。形を整えたパーツを、明示的な接続を持つ具体的なアフォーダンスに変換します。成果物はアフォーダンステーブルと図です。

UI アフォーダンス

#コンポーネントアフォーダンス操作出力先戻り先
U1letter-browser検索入力入力→ N1—
U2letter-browser読み込みスピナー描画——
U3letter-browser結果なしメッセージ描画——
U4letter-browser結果数描画——
U5letter-browser結果リスト描画→ U6, U7, U8, U9—
U6letter-row行のクリッククリック→ LD—
U7letter-row日付描画——
U8letter-row件名描画——
U9letter-rowティーザー描画——
U10letter-browserスクロールスクロール→ N11—
U11browser戻るボタンクリック→ N9—
U12letter-browser「全 X 件の結果を見る」クリック→ LP—
LD—レター詳細配置——
LP—全ページ配置——

コードアフォーダンス

#コンポーネントアフォーダンス操作出力先戻り先
N1letter-browseractiveQuery.next()呼び出し→ N2→ U12
N2letter-browseractiveQuery のサブスクリプション監視→ N3—
N3letter-browserperformSearch()呼び出し→ N4, → N6, → N7, → N8—
N4typesense.servicerawSearch()呼び出し—→ N3, → N12
N5letter-browserparentId(設定)設定—→ N4
N6letter-browserloading ストア書き込み—→ N8
N7letter-browserdetailResult ストア書き込み—→ N8, → N16
N8letter-browserdetectChanges()呼び出し→ U2, → U3, → U4, → U5—
N9browserURL ?q=読み取り→ N10—
N10letter-browserinitializeState()呼び出し→ N1, → N3—
N11intercom.serviceスクロール subject監視→ N12—
N12letter-browserappendNextPage()呼び出し→ N4, → N7, → N8, → N13, → N14—
N13intercom.servicesendMessage()呼び出し→ N11—
N14routernavigate()呼び出し—→ N9
N15letter-browser!compact の場合にサブスクライブ条件付き→ N11—
N16letter-browser切り詰められている場合にリンクを表示条件付き→ U12—
N17letter-browsercompact(設定)設定—→ N4, → N15, → N16
N18letter-browserfullPageRoute(設定)設定—→ U12

Mermaid 図

flowchart TB
    subgraph lettersIndex["PLACE: Letters Index Page"]
        subgraph letterBrowser["COMPONENT: letter-browser"]
            U1["U1: search input"]
            U2["U2: loading spinner"]
            U3["U3: no results msg"]
            U4["U4: result count"]
            U5["U5: results list"]
            U12["U12: See all X results"]

            N1["N1: activeQuery.next"]
            N2["N2: activeQuery sub"]
            N3["N3: performSearch"]
            N6["N6: loading store"]
            N7["N7: detailResult store"]
            N8["N8: detectChanges"]
            N10["N10: initializeState"]
            N16["N16: if truncated show link"]
            N5["N5: parentId (config)"]
            N17["N17: compact (config)"]
            N18["N18: fullPageRoute (config)"]

            subgraph pagination["PAGINATION"]
                U10["U10: scroll"]
                N15["N15: if !compact subscribe"]
                N12["N12: appendNextPage"]
            end
        end
    end

    subgraph letterRow["COMPONENT: letter-row"]
        U6["U6: row click"]
        U7["U7: date"]
        U8["U8: subject"]
        U9["U9: teaser"]
    end

    subgraph browser["BROWSER"]
        U11["U11: back button"]
        N9["N9: URL ?q="]
        N14["N14: Router.navigate"]
    end

    subgraph services["SERVICES"]
        N4["N4: rawSearch"]
        N11["N11: intercom subject"]
        N13["N13: sendMessage"]
    end

    subgraph letterDetail["PLACE: Letter Detail Page"]
        LD["Letter Detail"]
    end

    U1 -->|type| N1
    N1 --> N2
    N2 -->|debounce 90ms, min 3| N3

    N3 --> N4
    N3 --> N6
    N3 --> N7
    N3 --> N8

    N4 -.-> N3
    N4 -.-> N12
    N6 -.-> N8
    N7 -.-> N8

    N8 --> U2
    N8 --> U3
    N8 --> U4
    N8 --> U5

    U5 --> U6
    U5 --> U7
    U5 --> U8
    U5 --> U9

    U6 -->|navigate| LD
    U11 -->|restore| N9
    N9 --> N10
    N10 --> N1
    N10 --> N3

    U10 --> N11
    N15 -->|if !compact| N11
    N11 --> N12
    N12 --> N4
    N12 --> N7
    N12 --> N8
    N12 --> N13
    N12 --> N14
    N13 -->|re-arm| N11

    N5 -.->|filter| N4
    N17 -.-> N4
    N17 -.-> N15
    N17 -.-> N16
    N18 -.-> U12
    N14 -.->|URL| N9

    N1 -.-> U12
    N7 -.-> N16
    N16 -->|if truncated| U12
    U12 -->|navigate with ?q| LP["Full Page"]

    classDef ui fill:#ffb6c1,stroke:#d87093,color:#000
    classDef nonui fill:#d3d3d3,stroke:#808080,color:#000

    class U1,U2,U3,U4,U5,U6,U7,U8,U9,U10,U11,U12,LD,LP ui
    class N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12,N13,N14,N15,N16,N17,N18 nonui

ブレッドボードのスライス

ブレッドボード全体が完成したら、縦方向の段階に分割します。各スライスでは、機構が動作する様子を示します。

スライスの概要

#スライス機構アフォーダンスデモ
V1実データを使うウィジェットF1, F4, F6U2-U9, N3-N8, LD「ウィジェットに実データが表示される」
V2検索が機能するF3U1, N1, N2「『dharma』と入力すると、結果が絞り込まれる」
V3無限スクロールF5U10, N11-N13「下にスクロールすると、さらに読み込まれる」
V4URL の状態F2U11, N9, N10, N14「更新しても検索内容が保持される」
V5コンパクトモード—U12, N15-N18, LP「『すべて見る』リンクが表示される」

スライス図

flowchart TB
    subgraph slice1["V1: WIDGET WITH REAL DATA"]
        U2["U2: loading spinner"]
        U3["U3: no results msg"]
        U4["U4: result count"]
        U5["U5: results list"]
        U6["U6: row click"]
        U7["U7: date"]
        U8["U8: subject"]
        U9["U9: teaser"]

        N3["N3: performSearch"]
        N4["N4: rawSearch"]
        N5["N5: parentId (config)"]
        N6["N6: loading store"]
        N7["N7: detailResult store"]
        N8["N8: detectChanges"]
        LD["Letter Detail"]
    end

    subgraph slice2["V2: SEARCH WORKS"]
        U1["U1: search input"]
        N1["N1: activeQuery.next"]
        N2["N2: activeQuery sub"]
    end

    subgraph slice3["V3: INFINITE SCROLL"]
        U10["U10: scroll"]
        N11["N11: intercom subject"]
        N12["N12: appendNextPage"]
        N13["N13: sendMessage"]
    end

    subgraph slice4["V4: URL STATE"]
        U11["U11: back button"]
        N9["N9: URL ?q="]
        N10["N10: initializeState"]
        N14["N14: Router.navigate"]
    end

    subgraph slice5["V5: COMPACT MODE"]
        U12["U12: See all X results"]
        N15["N15: if !compact subscribe"]
        N16["N16: if truncated show link"]
        N17["N17: compact (config)"]
        N18["N18: fullPageRoute (config)"]
        LP["Full Page"]
    end

    U1 -->|type| N1
    N1 --> N2
    N2 -->|debounce| N3

    N3 --> N4
    N3 --> N6
    N3 --> N7
    N3 --> N8
    N4 -.-> N3
    N5 -.->|filter| N4

    N6 -.-> N8
    N7 -.-> N8
    N8 --> U2
    N8 --> U3
    N8 --> U4
    N8 --> U5
    U5 --> U6
    U5 --> U7
    U5 --> U8
    U5 --> U9
    U6 -->|navigate| LD

    U11 -->|restore| N9
    N9 --> N10
    N10 --> N1
    N10 --> N3

    U10 --> N11
    N11 --> N12
    N12 --> N4
    N12 --> N7
    N12 --> N8
    N12 --> N13
    N12 --> N14
    N13 -->|re-arm| N11
    N4 -.-> N12
    N14 -.->|URL| N9

    N15 -->|if !compact| N11
    N17 -.-> N4
    N17 -.-> N15
    N17 -.-> N16
    N18 -.-> U12
    N1 -.-> U12
    N7 -.-> N16
    N16 -->|if truncated| U12
    U12 -->|navigate with ?q| LP

    style slice1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style slice2 fill:#e3f2fd,stroke:#2196f3,stroke-width:2px
    style slice3 fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style slice4 fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px
    style slice5 fill:#fff8e1,stroke:#ffc107,stroke-width:2px

    classDef ui fill:#ffb6c1,stroke:#d87093,color:#000
    classDef nonui fill:#d3d3d3,stroke:#808080,color:#000

    class U1,U2,U3,U4,U5,U6,U7,U8,U9,U10,U11,U12,LD,LP ui
    class N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12,N13,N14,N15,N16,N17,N18 nonui

コメント