UI デザインを AI エージェントを使って模索する(その2)

ホーム画面だと要素が少ないので、カテゴリと商品注文の画面を使って模索してみる。

branch: ui-design-004

くら寿司の注文画面を作って Claude Code で作成してもらいます。

ちなみに、画面設計.md の カテゴリと商品一覧のページは次のように作られています。

## 画面 2:カテゴリ・商品一覧(`/categories`)

### 概要

メニューカテゴリの一覧と、選択カテゴリの商品一覧を表示する。カートへの追加もこの画面で行う。

### レイアウト(カテゴリ選択ビュー)

┌─────────────────────────────────────────────────────────┐
│  [← 戻る]  メニューを選ぶ           🛒 カート(3)→    │
│            (赤ヘッダー)                                  │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  メニューカテゴリを選択してください                       │
│                                                         │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐               │
│  │ 握り寿司 │ │ 巻き寿司 │ │ 軍艦巻き │               │
│  │  6品     │ │  4品     │ │  4品     │               │
│  └──────────┘ └──────────┘ └──────────┘               │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐               │
│  │ 海鮮丼  │ │ 特選寿司 │ │サイドメニュー│             │
│  │  3品     │ │  4品     │ │  3品     │               │
│  └──────────┘ └──────────┘ └──────────┘               │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐               │
│  │  汁物   │ │デザート  │ │  飲み物  │               │
│  │  2品     │ │  2品     │ │  3品     │               │
│  └──────────┘ └──────────┘ └──────────┘               │
│  ┌──────────┐                                          │
│  │ 季節限定 │                                          │
│  │  2品     │                                          │
│  └──────────┘                                          │
└─────────────────────────────────────────────────────────┘

### レイアウト(商品一覧ビュー)

┌─────────────────────────────────────────────────────────┐
│  [← 戻る]  メニューを選ぶ           🛒 カート(3)→    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  握り寿司                                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │ [商品画像]  まぐろ                  ¥580          │   │
│  │             旨みのある赤身          [カートに追加] │   │
│  └─────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────┐   │
│  │ [商品画像]  サーモン                ¥480         │   │
│  │             脂のりが抜群            [カートに追加] │   │
│  └─────────────────────────────────────────────────┘   │
│  ...                                                   │
└─────────────────────────────────────────────────────────┘

### UI 要素

| 要素 | 内容 | 備考 |
|------|------|------|
| ヘッダー | 赤背景、戻るボタン、カートバッジ | カート数が更新リアルタイム反映 |
| カテゴリグリッド | 3カラムグリッド | カテゴリ選択でビュー切り替え |
| 商品カード | 商品名・価格・説明・追加ボタン | 画像はプレースホルダー |
| カート追加ボタン | 赤ボタン | 押すたびに数量 +1 |

### カテゴリ一覧

| ID | カテゴリ名 | 商品数 |
|----|-----------|--------|
| 1 | 握り寿司 | 6 |
| 2 | 巻き寿司 | 4 |
| 3 | 軍艦巻き | 4 |
| 4 | 海鮮丼 | 3 |
| 5 | 特選寿司 | 4 |
| 6 | サイドメニュー | 3 |
| 7 | 汁物 | 2 |
| 8 | デザート | 2 |
| 9 | 飲み物 | 3 |
| 10 | 季節限定 | 2 |

### インタラクション

1. カテゴリカード押下 → 商品一覧ビューに切り替え
2. 「カートに追加」押下 → カートに商品追加(localStorage 更新)
3. 「←戻る」押下(商品ビュー)→ カテゴリビューに戻る
4. 「🛒 カート」押下 → `/cart` へ遷移
5. API エラー時 → ダミーデータで表示継続(エラーは無視)

律儀にカテゴリーの内容が記述してありますが、まあ、デモ画面としてはこれでよいかなと。実際は PHP で作った laravel-webapi の Web API を呼び出すので、そのコードを切り替えないといけません。

切り替え自体は、docs/openapi.yaml に OpenAPI 仕様があるので、次のプロンプトで切り替えられます。

docs/openapi.yaml の OpenAPI 仕様に従って、order-tablet のカテゴリ・商品一覧の画面を実装して

このレポジトリは web api の laravel-webapi とサンプルクライアントの order-tablet 等が混在しているので、対象となるプロジェクトを指定しないといけないのが面倒です。実際はクライアントとサーバーで別のレポジトリに分けるので、混乱は少ないと思います。
ただ、その分、設計書関係(agents/画面設計.md や 概要設計.md)が別フォルダーに配置されることになるので、このあたりのフォルダー構造は悩みどころ。

実際の Web API を呼び出して、「握り寿司」カテゴリーから商品一覧がでるようになっています。
openapi.yaml さえ作成してしまえば、クライアントサイドの lib/api.ts は AI エージェントで自動生成できるので、この部分は比較的簡単です。実際はサーバーのシステムが出来上がっていないことが多いので、なんらかのモックアップ用のサーバーを作ることになるのですが。Web API の仕様をクライアント側からチェックしたいときに使うとよいです。

branch: ui-design-005

サイゼリヤアプリのように、商品画像を表示せず、商品番号を入力する方式の画面を作ります。

src/order-tablet の 画面設計.md を対象とする。
カテゴリ・商品一覧から商品画像を削除する。
商品番号を直接入力する方式に変更。

画面設計.md にからカテゴリ一覧を削除します。

カテゴリ一覧を削除する。

トップ画面からのジャンプ先をカテゴリ一覧から商品一覧へと変更します。

ホーム画面から「カテゴリ一覧」ではなく、「商品一覧」に遷移する
商品一覧は /products とする。

このあたり、 /app/categories/page.tsx を /app/products/page.tsx に変更する形になるので、手作業だとちょっと大変なところですね。画面設計.md としては、「カテゴリ一覧」を「商品一覧」に変更してくれます。

## 画面 2:商品一覧(`/products`)

### 概要

メニューカテゴリの一覧と、選択カテゴリの商品一覧を表示する。カートへの追加もこの画面で行う。

### レイアウト(商品一覧ビュー)

┌─────────────────────────────────────────────────────────┐
│  [← 戻る]  メニューを選ぶ           🛒 カート(3)→    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  握り寿司                                               │
│  ┌─────────────────────────────────────────────────┐   │
│  │ No.001  まぐろ                          ¥580     │   │
│  │         旨みのある赤身                            │   │
│  └─────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────┐   │
│  │ No.002  サーモン                        ¥480    │   │
│  │         脂のりが抜群                              │   │
│  └─────────────────────────────────────────────────┘   │
│  ...                                                   │
├─────────────────────────────────────────────────────────┤
│  商品番号: [ No.___ ]  数量: [ 1 ]  [ カートに追加 ]    │
└─────────────────────────────────────────────────────────┘

### UI 要素

| 要素 | 内容 | 備考 |
|------|------|------|
| ヘッダー | 赤背景、戻るボタン、カートバッジ | カート数が更新リアルタイム反映 |
| カテゴリグリッド | 3カラムグリッド | カテゴリ選択でビュー切り替え |
| 商品カード | 商品番号・商品名・価格・説明 | 画像なし |
| 商品番号入力欄 | 数値入力(`No.XXX` 形式) | 画面下部に固定 |
| 数量入力欄 | 数値入力(デフォルト 1) | 1〜99 |
| カート追加ボタン | 赤ボタン | 商品番号・数量を検証してカートに追加 |

### インタラクション

1. カテゴリカード押下 → 商品一覧ビューに切り替え
2. 商品番号入力欄に `No.XXX` を入力 → 該当商品をハイライト
3. 数量入力欄に数量を入力(デフォルト 1)
4. 「カートに追加」押下
   - 商品番号が存在しない場合 → 「商品番号が正しくありません」エラー表示
   - 正常時 → カートに商品追加(localStorage 更新)、入力欄をリセット
5. 「←戻る」押下(商品ビュー)→ カテゴリビューに戻る
6. 「🛒 カート」押下 → `/cart` へ遷移
7. API エラー時 → ダミーデータで表示継続(エラーは無視)

番号の入力が、キーボードで入力できるように変更されています。

これは、番号ボタンに変えたいので、実装してから修正していきます。

コードを生成すると、カテゴリを表示した後に商品を選ぶのと、商品番号は左下で入力する方式になっているので、ちょっとイメージと違いますね。この部分は、画面設計.md で直接手作業で直したほうが早そうです。

## 画面 2:商品一覧(`/products`)

### 概要

商品を3桁の番号ボタンを使って選択する。
「注文追加」ボタンで、数量を指定するダイアログを出す。
「OK」ボタンで、カートに追加する

### レイアウト(商品一覧ビュー)

┌─────────────────────────────────────────────────────────┐
│  [← 戻る]  メニューを選ぶ           🛒 カート(3)→    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  商品番号: [ No.___ ]              
│  [ 1 ] [ 2 ] [ 3 ]
│  [ 4 ] [ 5 ] [ 6 ]
│  [ 7 ] [ 8 ] [ 9 ]
│        [ 0 ] [削除]
|
│             [注文追加]

└─────────────────────────────────────────────────────────┘

### UI 要素

| 要素 | 内容 | 備考 |
|------|------|------|
| ヘッダー | 赤背景、戻るボタン、カートバッジ | カート数が更新リアルタイム反映 |
| 商品カート | 商品番号・商品名・価格・説明 | 画像なし |
| 商品番号入力欄 | 数値入力(`No.XXX` 形式) | 画面上部に固定 |
| 数量入力欄 | 数値ボタン | 000 ~ 999 |
| 注文追加ボタン | 赤ボタン | 数量入力のダイアログを表示 |

### インタラクション

1. ホーム画面から商品一覧ビューに切り替え
2. 商品番号入力欄に `No.XXX` を入力 → 該当商品をハイライト
3. 数量入力ボタン欄に数量を入力
4. 注文追加ボタンで、数量ダイアログを表示
4. 「カートに追加」押下
   - 商品番号が存在しない場合 → 「商品番号が正しくありません」エラー表示
   - 正常時 → カートに商品追加(localStorage 更新)、入力欄をリセット
5. 「🛒 カート」押下 → `/cart` へ遷移
6. API エラー時 → ダミーデータで表示継続(エラーは無視)

まだ商品一覧表がでてきたり、なかなか狙った通りの画面になりませんが、何回かプロンプトを書き変えてできあがりまました。

できあがったら、画面設計.md も更新しておきます。

動作にあわせて 画面設計.md を修正して。

画面設計.md が書き変えられます。

## 画面 2:商品一覧(`/products`)

### 概要

数値ボタン(キーパッド)で商品番号を入力して商品を選択する。
「注文追加」ボタンで数量入力ダイアログを表示し、「OK」でカートに追加する。

### レイアウト

┌─────────────────────────────────────────────────────────┐
│  🍣 お寿司注文                       🛒 カート(3)→    │
│  テーブル: T-001                      (赤ヘッダー)     │
├─────────────────────────────────────────────────────────┤
│                                                         │
│              商品番号                                    │
│         ┌──────────────┐                               │
│         │   No.031     │  ← 入力中(一致なし: 赤枠)   │
│         └──────────────┘                               │
│         商品番号が正しくありません                        │
│                                                         │
│         ┌──────┐ ┌──────┐ ┌──────┐                   │
│         │  1   │ │  2   │ │  3   │                   │
│         ├──────┤ ├──────┤ ├──────┤                   │
│         │  4   │ │  5   │ │  6   │                   │
│         ├──────┤ ├──────┤ ├──────┤                   │
│         │  7   │ │  8   │ │  9   │                   │
│         └──────┘ ├──────┤ ├──────┤                   │
│                  │  0   │ │ 削除 │                   │
│                  └──────┘ └──────┘                   │
│                                                         │
├─────────────────────────────────────────────────────────┤
│              [ 注文追加 ]  (常に表示・赤ボタン)         │
└─────────────────────────────────────────────────────────┘

### 数量入力ダイアログ

┌──────────────────────────┐
│     数量を入力            │
│  ┌────────────────────┐  │
│  │ No.001  まぐろ      │  │
│  │        ¥150        │  │
│  └────────────────────┘  │
│  ┌────────────────────┐  │
│  │    1  個           │  │
│  └────────────────────┘  │
│  [ 1 ] [ 2 ] [ 3 ]       │
│  [ 4 ] [ 5 ] [ 6 ]       │
│  [ 7 ] [ 8 ] [ 9 ]       │
│        [ 0 ] [削除]       │
│  [ キャンセル ] [  OK  ]  │
└──────────────────────────┘

### UI 要素

| 要素 | 内容 | 備考 |
|------|------|------|
| ヘッダー | 赤背景、カートバッジ | カート数がリアルタイム反映 |
| 商品番号ディスプレイ | `No.___` 形式(最大3桁) | 一致商品あり: 赤枠・商品名と価格を表示 |
| キーパッド | 1〜9・0・削除 の数値ボタン | 削除は1桁ずつ消去 |
| 注文追加ボタン | 赤ボタン | 画面下部に常時固定表示 |
| 数量ダイアログ | 選択商品情報・数量キーパッド・OK/キャンセル | 数量0は OK 不可 |

### インタラクション

1. ホーム画面から `/products` へ遷移
2. 数値ボタンで商品番号を入力(最大3桁)
   - 一致する商品がある場合 → ディスプレイを赤枠にして商品名・価格を表示
3. 「削除」ボタン → 1桁ずつ削除
4. 「注文追加」押下
   - 商品番号が存在しない場合 → 「商品番号が正しくありません」エラー表示
   - 正常時 → 数量入力ダイアログを表示
5. ダイアログ内でキーパッドを使って数量を入力
6. 「OK」押下
   - 数量が 0 の場合 → 「数量を入力してください」エラー表示
   - 正常時 → カートに追加(localStorage 更新)・ダイアログを閉じる・入力欄リセット
7. 「キャンセル」押下 → ダイアログを閉じる
8. 「🛒 カート」押下 → `/cart` へ遷移
9. API エラー時 → ダミーデータで表示継続(エラーは無視)

このあたり、プロンプトを残しておくか、設計書として残しておくかの選択が必要なのですが、AI スパイラル開発(仮)としては、プロンプトで試行錯誤、本実装へは仕様書ベースで(設計→実装の順)ということになるので、ところどころ設計書を更新しておきます。
手書きのところががっつりと消されることもあるのですが、それは「AI が再実装できるような設計書」を作るということで AI 自身に書いて貰います。

branch: ui-design-006

今度は、商品一覧がずらっと横に並んでいるパターンを考えます。
カテゴリに分けてあるのではなくて、横に「次のページへ」で捲っていくパターンです。

画面設計.md を更新する

- カテゴリ一覧のページを削除する
- 商品一覧を、縦2 x 横3 ずつページにする。
- 「前へ」「次へ」ボタンでページをめくる
- 商品の上にカテゴリが表示される。

画面設計.md が書き換わったところで、実装を開始します。

## 画面 2:商品一覧(`/menu`)

### 概要

全カテゴリの商品を 2行×3列(6品)単位でページ表示する。各商品カードの最上部にカテゴリ名を表示する。カテゴリ選択画面は存在しない。

### レイアウト

┌─────────────────────────────────────────────────────────┐
│  [← 戻る]  メニューを選ぶ           🛒 カート(3)→    │
│            (赤ヘッダー)                                  │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │ 握り寿司 │  │ 握り寿司 │  │ 巻き寿司 │             │
│  │[商品画像]│  │[商品画像]│  │[商品画像]│             │
│  │ まぐろ   │  │ サーモン │  │ 鉄火巻き │             │
│  │  ¥580   │  │  ¥480   │  │  ¥450   │             │
│  │[カートへ]│  │[カートへ]│  │[カートへ]│             │
│  └──────────┘  └──────────┘  └──────────┘             │
│                                                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐             │
│  │ 巻き寿司 │  │ 軍艦巻き │  │ 海鮮丼  │             │
│  │[商品画像]│  │[商品画像]│  │[商品画像]│             │
│  │ ねぎとろ │  │ いくら  │  │ 海鮮丼  │             │
│  │  ¥420   │  │  ¥550   │  │  ¥980   │             │
│  │[カートへ]│  │[カートへ]│  │[カートへ]│             │
│  └──────────┘  └──────────┘  └──────────┘             │
│                                                         │
│        [← 前へ]    1 / 6ページ    [次へ →]             │
└─────────────────────────────────────────────────────────┘

/categoires が /menu に切り替わっているのが気になるところですが、このままいきます。

変更に従って実装して。

「前へ」「次へ」でページ送りができるようになっています。

これをもとにカテゴリが混在しないようにページを分けていきましょう。

メニューページを変更する

- カテゴリ単位で商品をページで区切る
- 画面の下部に「カテゴリー」を表示させて、カテゴリーをクリックすると商品の先頭にジャンプする

AI にうまく意図が伝わるかどうか不安だったのですが、ひとまず意図通りに動きができあがっています。

細かい動きは page/menu/page.tsx を確認することになるのですが、ここでは UI デザインの試行錯誤が目的なのでコードの内容は問わないことにします。

3つの試作を比較検討する

ここで商品表示の仕方を具体的に動かして比較してみます…と言いつつ、ブランチに分かれてしまっているので同時に動かせないのが難点なのですが。まあ、色合いは兎も角として、それぞれの画面をぽちぽちと動かしてみて、どの方式にするのかを選ぶことができます。いつもであれば、UI デザインとか全体のフォーマットを決めないとなかなか動くものが出て来ないのですが、AI エージェントを使えばそれなりのスピードで動くものが出てきます。

ただし、注意しないといけないのが

  • 動くものができたのだが、ちょっとニュアンスが違う部分をそのままにしてはいけない
  • AI が出したちょっと違ったものを「動くから、まあ、これでもいいか」と受け入れてはいけない

というのがあります。どうも、いままで実装をするまでに結構時間が掛かったので(それでも React とか Vue とかのフレームワークを使うと圧倒的に早いのですが)、細々とした UI を直す手間を開発者(私も含む)は惜しんでしまいそうになります。そういうといいは、「なんとしても、UI デザインにあわせる」という仕事的な努力が必要なのですが…そのあたりは、自分自身ではなくてデザイナーの力に頼ることになりそうですね。

おそらく、AI がデザインする UI にしても、典型的なパターンはいけそうなのですが、それ以上となると結構大変そうです。そういう意味では、HTML/CSS をこねくり回さなくても一定の画面がでてくるのが AI の良いところでもあり、それ以上に踏み込めない(デザイナーの人が AI を使ってデザインすると、変わるかも?)領域となりそうなので、この点に関しては、私はここでギブアップです。

逆に言えば、

  • そこそこ動作する画面は、AI エージェントを使って動作確認できるまではいける
  • 果たして、デザイナーが思い描いた UI デザインに AI エージェントは近づけられるのか?

という線引きがあります。ちなみに後者についていえば、経験上ですが、UI デザインに合わせるときはドット単位で手作業であわせていくのでプログラマでできないことはないです。ただし、非常に時間がかかります。なので、工数削減という意味では(あるいは、早期リリースや省力化による低価格化)前者の「AI によってそこそこ生成できる」部分と、「標準的なパーツを組み合わせて画面を構成する」という従来の画面作成のスタイルが踏襲できるといいですね。

サンプルコード

https://github.com/moonmile/webapi-sample-ai

  • branch: dev/order-tablet/ui-design-004
  • branch: dev/order-tablet/ui-design-005
  • branch: dev/order-tablet/ui-design-006

カテゴリー: 開発 | コメントする

UI デザインを AI エージェントを使って模索する

https://github.com/moonmile/webapi-sample-ai の order-tablet のコードをもとに、UI デザインを AI エージェントを使った模索します。方法としては、思いつきの実装を AI プロンプトと UI 設計で追加する試み https://www.moonmile.net/blog/archives/12159 のように、

  • 数行のプロンプトでコードを模索する

ことにします。先のブログでは、数行のプロンプトの試行が1回だけだったのですが、UI デザインの場合は、複数パターンを試していきましょう。通常のウォーターフォール開発やアジャイル開発では、UI デザインを「試案」という形で出して貰うことが多いでしょう。

AI エージェントを使った、AI スパライル開発では UI デザインも「試案」という形ではなく、もう一歩進んだ形で実際に動作するところまでやることができます。通常の開発プロセスならば、UI デザインを動かすところまで組みこんでしまうと手戻りが発生してしまい工数が掛かってしまうのですが、AI エージェントを利用した開発では、動作までのコーディングの工数は限りなくゼロに近くなります。実際には、AI エージェントがコーディングをするので、ちょっとした時間とそれなりの課金が発生してしまいますが、まあ、開発者の工数よりも安く付くでしょう…と仮定します。

基準の画面からスタートする

まずは、前回までのスパイラル開発で、Web API 等が確認できる UI までができているとします。

この状態から UI デザインを改良するとき、プロンプトや別の UI デザインを重ねることになります。

画面設計.md の中で、ホーム画面の UI デザインは次のように記述されています。

## 画面 1:ホーム(`/`)

### 概要

来店時に最初に表示される画面。来店人数を設定して注文を開始する。

### レイアウト

┌─────────────────────────────────────────┐
│         (グラデーション背景: 赤→オレンジ)  │
│                                         │
│    🍣 日経寿司へようこそ                   │
│    心を込めたお寿司をお楽しみください       │
│                                         │
│  ┌────────────────────────────────────┐ │
│  │  テーブル番号: T-001               │ │
│  │                                    │ │
│  │  何名様でお越しですか?              │ │
│  │                                    │ │
│  │     [−]    4    [+]               │ │
│  │                                    │ │
│  │       [ 注文開始 →]                │ │
│  └────────────────────────────────────┘ │
└─────────────────────────────────────────┘

### UI 要素

| 要素 | 内容 | 備考 |
|------|------|------|
| 背景 | 赤→オレンジのグラデーション | `from-red-600 to-orange-500` |
| テーブル番号 | `T-001`(ハードコード) | 実運用では動的取得 |
| 人数セレクタ | 1〜8名、デクリメント・インクリメントボタン | デフォルト 2 |
| 注文開始ボタン | 白背景・赤テキスト | クリックで `/categories` へ遷移 |

### インタラクション

1. `[−]` ボタン押下 → guests 減少(最小 1)
2. `[+]` ボタン押下 → guests 増加(最大 8)
3. 「注文開始」押下 → sessionStorage に `guests` 保存 → `/categories` へ遷移

この部分を変更するか、プロンプト少しずつ変更すれば UI デザインと実際の動作を確認することができるでしょう。

branch: ui-design-001

画面 1:ホーム(`/`)の修正

- 人数を 1~4人、5人以上のボタンに変更する
- ホーム画面のテーブル番号を動的に取得するようにする

GitHub Copilot で、画面設計.md を変更します。この部分は、手作業で 画面設計.md を変更しても構いません。

次に設計書からコードに反映させます。

フレームワークが React なので、ブラウザを表示したままで画面が変更されます。

## 画面 1:ホーム(`/`)

### 概要

来店時に最初に表示される画面。来店人数を設定して注文を開始する。

### レイアウト

┌─────────────────────────────────────────┐
│         (グラデーション背景: 赤→オレンジ)  │
│                                         │
│    🍣 日経寿司へようこそ                   │
│    心を込めたお寿司をお楽しみください       │
│                                         │
│  ┌────────────────────────────────────┐ │
│  │  テーブル番号: [動的取得]            │ │
│  │                                    │ │
│  │  何名様でお越しですか?              │ │
│  │                                    │ │
│  │  [1人] [2人] [3人] [4人] [5人以上] │ │
│  │                                    │ │
│  │       [ 注文開始 →]                │ │
│  └────────────────────────────────────┘ │
└─────────────────────────────────────────┘

### UI 要素

| 要素 | 内容 | 備考 |
|------|------|------|
| 背景 | 赤→オレンジのグラデーション | `from-red-600 to-orange-500` |
| テーブル番号 | 動的に取得 | API またはパラメータから取得 |
| 人数セレクタ | 1〜4人、5人以上のボタン | デフォルト 2 |
| 注文開始ボタン | 白背景・赤テキスト | クリックで `/categories` へ遷移 |

### インタラクション

1. 人数ボタン押下 → guests 設定(1〜4 または 5以上)
2. 「注文開始」押下 → sessionStorage に `guests` 保存 → `/categories` へ遷移

元の画面設計書には「グラデーション背景: 赤→オレンジ」とあるのですが、ちょっと揺れが生じています。
このあたりを正確に反映するには、Figma AI とかを使った方がよさそうです。

最初のブランチは残しておいて、次のブランチを試していきます。

branch: ui-design-002

再び master に戻してから  branch: ui-design-002 を作成しています。
このホーム画面ですが、メニューが表示されています。注文を開始した後のページであとメニューがあってもいいのですが、トップ画面は変ですね。このあたりを修正します。

画面 1:ホーム(`/`)の修正

- 人数を 1~8人、9人以上のボタンに変更する
- ホーム画面のテーブル番号を動的に取得するようにする
- ホーム画面のみメニューを外す。カテゴリ選択や注文のメニューはそのままにする
- テーマや背景の色を薄い緑色に統一する

ホーム画面は以下のように修正されます。

## 画面 1:ホーム(`/`)

### 概要

来店時に最初に表示される画面。来店人数を設定して注文を開始する。

### レイアウト

┌─────────────────────────────────────────┐
│      (グラデーション背景: 薄い緑)        │
│                                         │
│    🍣 日経寿司へようこそ                   │
│    心を込めたお寿司をお楽しみください       │
│                                         │
│  ┌────────────────────────────────────┐ │
│  │  テーブル番号: [動的取得]            │ │
│  │                                    │ │
│  │  何名様でお越しですか?              │ │
│  │                                    │ │
│  │  [1~8人] [9人以上] のボタン        │ │
│  │                                    │ │
│  │       [ 注文開始 →]                │ │
│  └────────────────────────────────────┘ │
└─────────────────────────────────────────┘

### UI 要素

| 要素 | 内容 | 備考 |
|------|------|------|
| 背景 | 薄い緑のグラデーション | `from-green-50 to-green-100` |
| テーブル番号 | 動的に取得 | URLパラメータから取得 |
| 人数セレクタ | 1~8人、9人以上のボタン | デフォルト 2、選択ボタン方式 |
| 注文開始ボタン | 白背景・緑テキスト | クリックで `/categories` へ遷移 |
| メニュー表示 | なし | ホーム画面のみヘッダーメニューを非表示 |

### インタラクション

1. 人数ボタン押下 → guests 設定(1~8 または 9以上)
2. 「注文開始」押下 → sessionStorage に `guests` 保存 → `/categories` へ遷移

メニューの形式が変わるので layout.tsx などがコード変更対象になります。

画面はこんな風に変更になります。

カテゴリーを表示したときに、メニューと「カート」の表示が分離されているので、これをひとつにします。

カテゴリー画面や注文履歴を表示したときに、ホームやカテゴリーなどのメニューと、「カート」や「お会計」をひとつのメニューになるように直して。

このあたりは、プロンプトから指示したほうがいいのか、Header.tsx や layout.tsx をコードで直したほうがいいのかわかりませんが、ひとまず、思った感じになるように修正していきます。

- 「日経寿司タブレット」のメニューだけ残して、「お寿司注文」のヘッダーは削除する
- 「日経寿司タブレット」に追加した「カート」には、注文数を表示して

メニューにある「カート」の数字が変わらないのですが、ひとまずデザインとしてはこのままにして、次の案を試していきます。

branch: ui-design-003

再びブランチを master に戻して ui-design-003 を作成します。
今度は、ホーム画面のデザインを大幅に変更します。ボタンの形を丸型にしてみましょう。

画面 1:ホーム(`/`)の修正

- 人数を 1~8人、9人以上のボタンに変更する。ボタンは大き目の丸型ボタン
- ホーム画面のテーブル番号を動的に取得するようにする
- ホーム画面の背景には今月のお薦めの写真を3枚入れる

という形で Git でブランチを分けて「数行のプロンプト」の結果を分けて保存していきます。全体的なデザインとかそれに追随する動きを確認するために、プロトタイピングの工数が減らせますよね…という進め方を考えていたのですが、

  • ブランチが分かれてしまうので、相互の動作を比較しにくい
  • 細かい修正をしたいときに、ブランチを切り替えないと直せないので結構面倒

という欠点があって、意外と比較して動かすことができません orz 同時に試してみたいのであれば、3つぐらいのブラウザで同時に動かすぐらいの芸当は見せたほうがいいので、試行錯誤ができるデザインプロセスとしてはいまいちですね。

まあ、それにしても、モックを手作業で作らずにクライアントのデザインができるのでは?の一歩としています。

あと、最初のホーム画面だけだと人数の入力のみで機能がすくないので、同じような実験として、カテゴリ表示と商品選択(お寿司の選択)のところを AI のプロンプトで組んでみます。

サンプルコード

https://github.com/moonmile/webapi-sample-ai

  • branch: dev/order-tablet/ui-design-001
  • branch: dev/order-tablet/ui-design-002
  • branch: dev/order-tablet/ui-design-003

おまけ

という感じで、この本ブログは続くわけですが、技術ブログのひとつとして紹介してくださるそうなので、以下を見てくださると分かります。

スキルアップやキャッチアップにつながる!注目のITブログ・メディアまとめ その2 via レバレジーズ株式会社

https://freelance-hub.jp/column/detail/697/#bmf-tech

昨今、ChatGPT とか Copilot とかで技術情報の初手を探すことが多くなったので、なかなか技術ブログを直接みることが少なくなっているのですが、単なる情報ではなくて「実際にやってみたけど、うまくいかなかった」とか「初手の手順はわかったけど、もう一歩進めるための方向性がわからない」ってなときに、従来型の技術ブログが役に立ちます。

他のブログを見ていくと、本ブログよりも綺麗にまとまった情報が多くて恐縮なのですが…本ブログの形式としては、興味あるところにちょっとずつ踏み込んでいく過程を記録しておくことが多いので、ご勘弁ください。執筆する書籍のほうはどうしても一冊としてまとまりがあるもの(あるいは結論があるもの)にならざるを得ないのですが、バックグラウンドとしては、こんな風な思考錯誤と興味の網を張って試している備忘録なのです。

カテゴリー: 開発 | コメントする

BLE-chat 実機の疎通確認コードを追加する

BLE を使った Android 実機の自動テストコードを考えます。AI を含めたスパイラル開発では、Web API を使ったほうが実用的なような気がするのですが…ひとまず BLE のほうからやってみます。Web API 利用のほうは、先日執筆した PHP & Laravel 開発のサンプルコードを使っていずれやってみます。
テストの基準にもいろいろあるのですが、

  • 動作確認を自動化するための自動実行コード
  • 設計から生成されたコードを確認するためのテストコード
  • 完成品の品質を確認するためのテストコード

あたりから手をつけていきましょう。これも、あれこれ考えこむよりも、ひとまずテストコードを書いて動かしてみてから良い方法を考えるというアジャイル方式です。

動作確認をするための疎通確認コード

BLE を使った Android アプリの場合、いちばん面倒臭いのが疎通確認です。電波の状態が届いているのか、ライブラリがきちんと動作しているのか、送受信がうまくいっているのか、という諸々の理由から、ビルドして新しいアプリを入れ直したときに疎通確認は必須です。なんといっても、BLE が繋がっていないままにテストコードを動かしても「全然繋がらない」ために、原因を特定するのたいへんです。アプリのあれこれの動作を確認するために「確実に2台で繋がっている」あるいは「確実にメッセージが送受信されている」という状態を確保しなければいけません。ネットワーク系のテストをするときにはここが最重要ですね。

いくつか方法があると思うのですが、ひとまず Claude Code に聞いてみましょう。

BLE の疎通確認が自動化できるようにしたい。どんな方法がある?

基本は BLE を使わないで単体テストで動作する方法なのですが、これは後で追加するとして… Test Uiautomator https://developer.android.google.cn/jetpack/androidx/releases/test-uiautomator?hl=ja#2.4.0 が使えるそうなので、これを試してみます。

先のスパイラル開発に従って「数行のプロンプト」を入れてテストの動作確認コードをいれていきます。いきなりテスト設計書レベルのものは作りません。

実機2台と Uiautomator を使って、以下のテストコードを書いて

- 「Hello」を1回だけ送信するテストコード
- 「Hello」+ 通番 を1秒おきに10回送信するテストコード
- 「Hello」+ 通番 を5秒おきに5回送信するテストコード
- 受信したメッセージ adb コマンドで出力するコード

adb コマンドを使って、androidTest を呼び出すことができます。

BleTestBase.kt 内で uiautomator を使って操作

package net.moonmile.ble5_chat.claude

import android.content.Intent
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.junit.Before

/**
 * BLE チャットテストの共通基底クラス。
 *
 * 起動モードが 2 種類ある:
 *   - setUp()              … アプリを完全に再起動(BLE 初期化からやり直す)
 *   - attachToRunningApp() … 既に起動しているアプリにアタッチ(BLE 状態を維持)
 *
 * attachToRunningApp() を使う場合は、サブクラスで setUp() をオーバーライドして呼ぶ。
 */
abstract class BleTestBase {

    protected lateinit var device: UiDevice

    companion object {
        const val PACKAGE        = "net.moonmile.ble5_chat.claude"
        const val LAUNCH_TIMEOUT = 8_000L   // アプリ起動待機 (ms)
        const val BLE_INIT_WAIT  = 2_000L   // BLE 初期化待機 (ms)  ※フル起動時のみ使用
        const val ATTACH_WAIT    = 500L     // アタッチ後の UI 安定待機 (ms)
        const val SEND_SETTLE    = 500L     // 送信後の UI 安定待機 (ms)
        const val TAG            = "BleChatTest"
    }

    // ── フル起動モード ────────────────────────────────────────────
    /**
     * アプリを完全に再起動する。
     * タスクをクリアして MainActivity を新規起動し、BLE 初期化まで待機する。
     */
    @Before
    open fun setUp() {
        val instrumentation = InstrumentationRegistry.getInstrumentation()
        device = UiDevice.getInstance(instrumentation)

        // ホームに戻してから起動(前回の状態をリセット)
        device.pressHome()

        val intent = instrumentation.context.packageManager
            .getLaunchIntentForPackage(PACKAGE)
            ?.apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) }
            ?: error("パッケージが見つかりません: $PACKAGE")

        instrumentation.context.startActivity(intent)

        // アプリウィンドウが表示されるまで待機
        device.wait(Until.hasObject(By.pkg(PACKAGE).depth(0)), LAUNCH_TIMEOUT)

        // BLE 初期化(スキャン開始)を待機
        Thread.sleep(BLE_INIT_WAIT)

        Log.i(TAG, "setUp: アプリ起動完了(フル起動)")
    }

    // ── アタッチモード ────────────────────────────────────────────
    /**
     * 既に起動しているアプリにアタッチする。
     *
     * FLAG_ACTIVITY_CLEAR_TASK を付けずに startActivity() を呼ぶことで、
     * 既存のタスクをそのままフォアグラウンドに戻す。
     * BLE はすでに初期化・スキャン中の状態が維持される。
     *
     * サブクラスで setUp() をオーバーライドして呼ぶ:
     *
     *   @Before
     *   override fun setUp() = attachToRunningApp()
     */
    protected fun attachToRunningApp() {
        val instrumentation = InstrumentationRegistry.getInstrumentation()
        device = UiDevice.getInstance(instrumentation)

        // フラグなし = 既存タスクをフォアグラウンドに戻すだけ(Activity は再生成されない)
        val intent = instrumentation.context.packageManager
            .getLaunchIntentForPackage(PACKAGE)
            ?: error("パッケージが見つかりません: $PACKAGE")
        // FLAG_ACTIVITY_CLEAR_TASK を意図的に付けない

        instrumentation.context.startActivity(intent)

        // ウィンドウがフォアグラウンドに来るまで待機
        device.wait(Until.hasObject(By.pkg(PACKAGE).depth(0)), LAUNCH_TIMEOUT)

        // UI 安定待機のみ(BLE 再初期化は不要)
        Thread.sleep(ATTACH_WAIT)

        Log.i(TAG, "attachToRunningApp: 既存アプリにアタッチ完了")
    }

    /**
     * メッセージを入力して送信する。
     *
     * @param text 送信するテキスト
     * @throws IllegalStateException 入力欄または送信ボタンが見つからない場合
     */
    protected fun sendMessage(text: String) {
        // テキスト入力欄を取得(Compose の OutlinedTextField は EditText として見える)
        val inputField = device.findObject(By.clazz("android.widget.EditText"))
            ?: error("入力フィールドが見つかりません。画面が正しく表示されているか確認してください。")

        inputField.clear()
        inputField.setText(text)

        // 送信ボタンをタップ(contentDescription = "送信")
        val sendButton = device.findObject(By.desc("送信"))
            ?: error("送信ボタンが見つかりません。BLE が ON になっているか確認してください。")

        sendButton.click()

        // UI が安定するまで待機
        Thread.sleep(SEND_SETTLE)
    }
}

連続送信を試す BleChatSendTest.kt

@RunWith(AndroidJUnit4::class)
class BleChatSendTest : BleTestBase() {

    /**
     * アプリを再起動せず、既に起動している BLE セッションにアタッチする。
     * BLE の初期化・スキャン状態は維持されるため、送信可能になるまでの待機が不要。
     */
    @Before
    override fun setUp() = attachToRunningApp()

    // ── テスト1: 「Hello」を1回送信 ────────────────────────────────
    @Test
    fun sendHelloOnce() {
        Log.i(TAG, "=== sendHelloOnce 開始 ===")

        sendMessage("Hello")
        Log.i(TAG, "[SEND] Hello")

        Log.i(TAG, "=== sendHelloOnce 完了 ===")
    }

    // ── テスト2: 「Hello + 通番」を1秒おきに10回送信 ───────────────
    @Test
    fun sendHelloTenTimesEverySecond() {
        val total    = 10
        val interval = 1_000L   // 1秒

        Log.i(TAG, "=== sendHelloTenTimesEverySecond 開始(${interval}ms 間隔 × $total 回)===")

        repeat(total) { index ->
            val seq = index + 1
            val msg = "Hello $seq"

            sendMessage(msg)
            Log.i(TAG, "[SEND][$seq/$total] $msg")

            if (seq < total) {
                Thread.sleep(interval)
            }
        }

        Log.i(TAG, "=== sendHelloTenTimesEverySecond 完了 ===")
    }

    // ── テスト3: 「Hello + 通番」を5秒おきに5回送信 ────────────────
    @Test
    fun sendHelloFiveTimesEveryFiveSeconds() {
        val total    = 5
        val interval = 5_000L   // 5秒

        Log.i(TAG, "=== sendHelloFiveTimesEveryFiveSeconds 開始(${interval}ms 間隔 × $total 回)===")

        repeat(total) { index ->
            val seq = index + 1
            val msg = "Hello $seq"

            sendMessage(msg)
            Log.i(TAG, "[SEND][$seq/$total] $msg")

            if (seq < total) {
                Thread.sleep(interval)
            }
        }

        Log.i(TAG, "=== sendHelloFiveTimesEveryFiveSeconds 完了 ===")
    }
}

Uiautomator の使い方がややこしいので、二の足を踏んでいたのですが、AI コーディングでここまで出来上がるのであれば、AI に任せてしまったほうが楽です。

疎通確認のためのデバッグ画面を作成する

ただし、このままだといちいち adb コマンドを叩かないといけないので、疎通確認用のデバッグ画面を追加します。

実機で疎通確認ができるように、デバッグ用の画面を「設定」→「疎通確認」という形で追加

- 「Hello」を1回だけ送信するテストコード
- 「Hello」+ 通番 を1秒おきに10回送信するテストコード
- 「Hello」+ 通番 を5秒おきに5回送信するテストコード
- 受信したメッセージをリストに表示

できあがったら、Android Studio でビルドして実機で動作確認します。

定番のボタンを押して、疎通をする簡単な画面なのですが、これがあると結構便利です。システムを作るときに、こんな感じで開発用のデバッグ画面を用意しておくと手軽にボタンやリスト表示などができて便利です。いままでは、画面を作るのにちょっと手間がかかって億劫な感じだったのですが、AI コーディングでここまで出来てくれればこれで十分です。

この画面自体は、レイアウトは特にこだわらないので AI コードディングのままで済ませておきます。

あと、2台だと疎通だけで解り辛いのですが、Android が 3台以上あると、同時に複数台に BLE アドバタイズで送信していることがわかります。

これ、実際に動作するまでわからなかったのですが、意外と BLE5 の拡張アドバタイズだと抜けがでますね。右の端末からアドバタイズ発信を1秒毎に切り替えて発信しているのですが、真ん中の端末では10件すべて受信していますが、左の端末ではとびとびで5件しか受信できていません。

これは、BLE アドバタイズを受信するときにある程度遅延するので、そこで取りこぼしが発生していると考えられます。拡張アドバタイズの場合、UDP 通信のように通信データが必ず届くとは限らないので、これはこれで正しい動作です。

ただ、なにか変なタイミングで落ちているのが気になるところ…

2026-04-30 10:08:18.601 14575-14575 BLE5Chat/B...nerManager net.moonmile.ble5_chat.claude        D  Duplicate filtered: 3d71fb74-69da-44ad-ab9d-15205c8f437f
2026-04-30 10:08:18.726 14575-14575 BLE5Chat/B...nerManager net.moonmile.ble5_chat.claude        D  Duplicate filtered: d44b0b62-7253-4a11-bb7d-052e0c82b652
2026-04-30 10:08:18.868 14575-14575 BLE5Chat/B...nerManager net.moonmile.ble5_chat.claude        D  Received: 870038ea-059e-43b7-a4e5-611339da2d92 from 11112222
2026-04-30 10:08:18.869 14575-14575 BLE5Chat/MainActivity   net.moonmile.ble5_chat.claude        D  Received: 870038ea-059e-43b7-a4e5-611339da2d92 from 11112222
2026-04-30 10:08:18.943 14575-14575 AndroidRuntime          net.moonmile.ble5_chat.claude        D  Shutting down VM
2026-04-30 10:08:18.948 14575-14575 AndroidRuntime          net.moonmile.ble5_chat.claude        E  FATAL EXCEPTION: main (Fix with AI)
                                                                                                    Process: net.moonmile.ble5_chat.claude, PID: 14575
                                                                                                    java.lang.IllegalArgumentException: Key "870038ea-059e-43b7-a4e5-611339da2d92" was already used. If you are using LazyColumn/Row please make sure you provide a unique key for each item.
                                                                                                    	at androidx.compose.ui.internal.InlineClassHelperKt.throwIllegalArgumentException(InlineClassHelper.kt:36)
                                                                                                    	at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:1366)
                                                                                                    	at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:1231)
                                                                                                    	at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.compose(LazyLayoutMeasureScope.kt:94)
                                                                                                    	at androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItemProvider.getPlaceables-3p2s80s(LazyLayoutMeasuredItem.kt:60)
                                                                                                    	at androidx.compose.foundation.lazy.LazyListMeasuredItemProvider.getAndMeasure-0kLqBqw(LazyListMeasuredItemProvider.kt:53)
                                                                                                    	at androidx.compose.foundation.lazy.LazyListMeasuredItemProvider.getAndMeasure-0kLqBqw$default(LazyListMeasuredItemProvider.kt:47)
                                                                                                    	at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-LCrQqZ4(LazyListMeasure.kt:224)

おそらく、ダブりで受信「Received: 870038ea-059e-43b7-a4e5-611339da2d92 from 11112222」したときに、リストに追加する際にキーが重複しているような気がします。ここは後で。

簡単なテストコードを書いておく

BLE チャーッとツールには、明確なロジッククラスがあるわけではないので、わざわざテストコードを書く必要もないのですが、「設計から生成されたコードを確認するためのテストコード」という観点から、簡易的なテストコードを追加しておきます。

いわゆるクラス設計の通りにコーディングされている否か、をチェックします。

### 1. クラス一覧

| クラス名 | 種別 | 主な責務 |
|---|---|---|
| MainActivity | UI Host | Compose エントリポイント、`setContent`、UI 状態管理、ライフサイクル連携 |
| ChatScreen | UI Compose | 純粋な描画(State を受け取りコールバックを呼ぶ) |
| ChatRepository | Domain/Application | 送受信ユースケース統合、履歴の仲介(IF) |
| ChatRepositoryImpl | Data | `BleChatService` を使った Repository 実装 |
| BleChatService | BLE Facade | BLE 初期化、Advertiser/Scanner の開始停止管理 |
| BleAdvertiserManager | BLE Tx | Extended Advertising によるメッセージ送出 |
| BleScannerManager | BLE Rx | 広告パケット受信、重複排除 |
| MessageCodec | Protocol | 文字列とバイト列の変換(最大 100 文字制約含む) |
| DispatcherProvider | Concurrency | Coroutine Dispatcher の抽象化(テスト差し替え用) |
| DuplicateFilter | Reliability | MessageId による重複受信抑止 |
| PeerRegistry | Session | 参加端末の最終受信時刻管理(参加者把握) |
| FavoriteRepository | Data | お気に入りメッセージの追加・削除・永続化(SharedPreferences + JSON)、StateFlow で変更を通知 |
| ChatMessage | Model | チャット本文、送信者 ID、時刻、MessageId |
| AdvPacket | Model | 1広告単位データ(MessageId、Seq、Total、Payload) |
| ChatUiState | UI Model | Compose 描画用状態(メッセージ、入力値、送信可否、エラー) |
| ChatUiEvent | UI Model | UI 操作イベント(送信、入力更新、開始、停止) |
| ChatUiEffect | UI Model | 1回性イベント(Toast、権限要求誘導) |
| AppLogger | Cross-cutting | ログ収集(テスト・障害解析用) |
| ErrorHandler | Cross-cutting | BLE 例外、権限エラー、BLE OFF 検知、復旧リトライ判断 |

今回のスパイラル開発では基本的に、コードから 概要設計.md を再作成しているので、ここのクラス一覧は正しいはずです。人間の開発者ならば、設計→実装というプロセスの中で、設計通りにコーディングがされているのか?を「実装工程」としてチェックしていきます。ここは、コードレビューとか、ヒアリングとか、あるいはテスト工程でのチェックが入ります。

同じパターンを AI コーディングのプロセスでも使えないか、という試みです。

おそらく、クラス名やメソッド名をコードを検索して確認するツールがあれば十分だと思うのですが、ここでは簡易的なテストコードを書いて確認します。いわゆる、テスト駆動からの借用ですが、テストコードを先に書いて実装という流れではなく、実装コードに合わせてテストコードを書きます。当たり前ですが、必ず通るテストコードになり、このままでは意味がありません。

スパイラル開発で、次の「設計生成」が行われて「コード生成」が実行されたときに、不意にクラスやメソッドが消えていないことを確認するためのテストコードです。もちろん、設計自体が変わってしまえばテストコードの変更も必須なのですが、現時点ではコードの変更だけをチェックする簡易テストコードに絞ります。

実機の BLE を使わない単体テストコードを追加する

- net.moonmile.ble5_chat.claude.ble.MessageCodec
- net.moonmile.ble5_chat.claude.ble.DuplicateFilter
- net.moonmile.ble5_chat.claude.ble.PeerRegistry
- net.moonmile.ble5_chat.claude.util.ErrorHandler
- net.moonmile.ble5_chat.claude.util.DispatcherProvider
- net.moonmile.ble5_chat.claude.util.DefaultDispatcherProvider

データクラスの簡易テストも追加する

- net.moonmile.ble5_chat.claude.model.ChatMessage
- net.moonmile.ble5_chat.claude.model.AdvPacket
- net.moonmile.ble5_chat.claude.model.ChatUiState
- net.moonmile.ble5_chat.claude.model.ChatUiEffect
- net.moonmile.ble5_chat.claude.model.ChatUiEvent

形式的な型チェックなので、実機を使わないでテストができるクラスだけを選んでいます。

![](images/20260430_06.jpg)

コマンドラインで

./gradlew test

とすると、JUnit のテストコードが動きます。

![](images/20260430_07.jpg)

基本的に Claude Code がテストコードを書いたあとに、自らテストコードがすべて成功するまで書き直してくれるので、初回のテストコードはすべて成功するに決まっています。

中身は簡単なものばかり≒テストが通るものばかりなので、内容はあまり関係ありません。次回以降

  • 数行のプロンプトでコード生成をしたとき
  • 設計書からコード生成をしたとき

この後で、単体テストのコードを動かして「既存のコードが壊れていない事」を確認すれば ok です。
あるいは、リファクタリング目的や非互換のコードを含めようとしたときは、再び動作確認用のテストコードに AI が手を入れる、ということになります。

mock を使ったテストコードを組みこむ

  • チャットの保持 ChatRepositoryImpl
  • お気に入りの保持 FavoriteRepository

これらは、内部データに BleChatService や SharedPreferences を使っているため、単体テストコードでは動かせません。これらに対してはモック化をして、テストコードを動かせるようにします。
SharedPreferences による永続化に関しては androidTest で動かすほうがいいのですが、いったん、ここはモック化にしておきます。

モック化を使って以下のクラスのテストコードを追加

- ChatRepositoryImpl
- FavoriteRepository

途中がよくわからないのですが、テストコードを作った後に失敗してコードをやり直ししていたりしますね。

テストコードの内容はさておき、おおまかなクラスやメソッドを固定化することができました。これで、次回以降コードに何かを付け加える場合は、「既存のインターフェースを変えないように慎重にコードを変更するか」あるいは「テストコードの修正も含めて代替に設計/コード変更を行うか」を選ぶことができます。

大胆な変更としては、

  • ChatMessage データクラスを変えて BLE アドバタイズのデータ形式を変更する
  • ChatRepository を複数持てるようにして、ルームの変更などに対応する
  • チャットの配信機能を、文字ではなく色や図形などに固定して、チャット画面の UI を大幅に変更する

のような形です。

ここまで来ると、人間が開発する場合は「新しく作り直したほうが手っ取り早い」という形になりそうなのですが、果たして AI コーディングをする場合にはどうすれいいでしょうか? という話ですね。FolkBears の場合は、BLE の通信部分(GATT, Advertiser, Scanner)をライブラリ化して、それを流用する設計にしたのですが、この BLE チャットツールの場合はどうなるのか?

コード

https://github.com/moonmile/BLE-chat

  • branch: dev/test-network-cluade : 単体テストコードの追加
カテゴリー: 開発 | コメントする

思いつきの実装を AI プロンプトと UI 設計で追加する試み

追加のスパイラル開発として、チャット画面から特定のメッセージを保存できるようにします。
いわゆる、「お気に入り」機能のようなものを、お試しで実装してみようという訳です。

この手の試しに機能は、実装したときのユーザーの効果はあるのか?という問題もありますが、実装してみないとわからないというものが多いです。従来の開発であれば、実装するのも、設計書を書くのも時間が掛かるので「その実装は、本当に意味があるのか?」と事前に議論することが多いでしょう。
しかし、プロトタイプでの工程やモックアップを使ったコードの場合には、実際に動くものを見て考えたほうが議論するよりも早いというのはよくあることです。
それではあっても、モックアップを作ること自体、既存のコードに何かを組み入れて動かすこと自体が手間なことが多いです。

が、先の AI を使ったスパイラル開発の方式で考えれば、

1. 数行のプロンプトでざっくりとしたコードを作る
2. できあがったコードを動かしてみる
3. 画面の動きやコードから、AI で UI 設計や概要設計など逆生成する
4. 出来上がった UI 設計や概要設計を見直す
5. 画面設計や概要設計に従って、コードを再作成する

というサイクルを作ればよさそうです。
UI 設計や概要設計は、コードを生成するときの再現性を高めておくためです。ある程度、開発が進んでしまえばいらなくなる markdown 形式の設計書ですが、プロンプトをちまちまと残しておいて生成するよりも、再現性は高いと思われます。

数行のプロンプトを作る

チャット画面から「お気に入り」に入れられる機能を作ります。
- チャット画面のメッセージをチェックすると「お気に入り」に入れられる
- 「お気に入り」の画面で一覧が確認できる
- 「お気に入り」の画面では、指定したメッセージを削除できる

これを Claude Code で試します。
ベースととなるのは、dev/add-setting-ui-claude ブランチのコードです。

画面的にはチャット画面(ChatScreen.kt)とお気に入り画面(FavoritesScreen.kt)に手を入れることになるので、単機能という訳ではありません。
テスト駆動として作るならば、

1. FavoriteRepository.kt のテストコードを作成する
2. FavoriteRepository.kt を作成する
3. ChatScreen.kt と結合する
4. FavoritesScreen.kt を作成する
5. FavoritesScreen.kt を結合する
6. UI の操作をテストする

という感じでしょうか。最初の「数行のプロンプト入力」の場合は、動きだけを確認して UI 設計を作成することが目的なので、この時点ではテストコードは不要かなと。

できあがったコードを動かす

サンプルとして出来上がったものがこんな感じです。
星マークでメッセージをタップして、お気に入り画面に入れられます。削除ボタンはゴミ箱になっています。
メニューに「★」が追加されているので、今後はメニューが増えたときにどうなるのかわかりませんが、ひとまず動作的には大丈夫そうな感じですね。

試しにアプリを落として再立ち上げしても、お気に入りの内容は保持されています。

repository/FavoriteRepository.kt

// ── 永続化 ─────────────────────────────────────────────────────
private fun load(): List<ChatMessage> = runCatching {
    val json = prefs.getString(KEY, "[]") ?: "[]"
    val arr  = JSONArray(json)
    (0 until arr.length()).map { i ->
        val obj = arr.getJSONObject(i)
        ChatMessage(
            messageId = obj.getString("messageId"),
            senderId  = obj.getString("senderId"),
            timestamp = obj.getLong("timestamp"),
            text      = obj.getString("text")
        )
    }
}.getOrDefault(emptyList())

private fun save(list: List<ChatMessage>) {
    val arr = JSONArray()
    list.forEach { msg ->
        arr.put(JSONObject().apply {
            put("messageId", msg.messageId)
            put("senderId",  msg.senderId)
            put("timestamp", msg.timestamp)
            put("text",      msg.text)
        })
    }
    prefs.edit().putString(KEY, arr.toString()).apply()
}

コードを修正する

お気に入りの画面で、ごみ箱のアイコンをタップすると、即座にメッセージが消されてしまうと困るので、この部分を変更しましょう。

- お気に入りの画面で、メッセージをスライドしたときにゴミ箱アイコンがでるように変更
- ゴミ箱アイコンをタップしたときに、リストから削除する

ビルドをして実機で実行するとこんな感じ。

追加した機能を UI 設計書に書き出す

複数のプロンプトをまとめておくために、UI 設計書に書き出します。既存の UI 設計書に追記してもいいのですが、ここでは別途 agents/UI設計書_お気に入り.md を作成して、そこに書き出すことにします。

追加した機能を agents/UI設計書_お気に入り.md に書き出して

たぶん、あとから複数の UI 変更をひとつにまとめた方が効率がいいはずです(これも後で試してみましょう)。

出来上がった、agents/UI設計書_お気に入り.md はこんな感じです。

出来上がった UI 設計や概要設計を見直す

果たして、この UI 設計書が今後のリファクタリング等に耐えられるかどうかは判断しかねます。UI 設計にしては、コードのファイル名やメソッド名が直書きになっているので、コード変更に弱い感じがしますが…アプリ開発時の一時的な参考としてはこれで十分でしょう。
すくなくとも、UI 設計書から Claude Code や GitHub Copilot などの異なる AI エージェントを使っても、同じようなコード(完全に同じとは言えないが)が生成される「再現性」が担保できれば ok です。

- 「お気に入り機能」を 概要設計.md に追加 
- UI 設計書をひとつにまとめる

作業を追加しておきます。いわゆる、機能追加や仕様変更を、設計書に反映する作業です。

いわゆる、情報のトレーサビリティの問題で、概要設計書やUI設計書から過不足なくコードに反映されていればよいという発想です。細かいところは、かつては詳細設計という形で詰めていたのですが、昨今はコードが残ればだいたい ok です。
コードからドキュメントを再作成するツールはかつてもありましたが、UML の出力やクラスのメソッドの一覧などの留まっていました。

現在ならば、AI でコードから生成した設計書をもとに、AI 自身が設計書をもとにコードを再生成ことも可能だろう、という考え方です。

まとめてしまったので、設定とお気に入りの UI 設計書は削除してしまいます。

画面設計や概要設計に従って、コードを再作成してみる

修正した概要設計と画面設計を使って GitHub Copilot でコードを再作成してみます。
ベースはお気に入りの機能を入れる前の branch: dev/add-setting-ui-claude を使って試します。

概要設計.md と UI設計書.md に従ってコードを変更して

AI が設計書の差分から確認し始めるので、なんらかの形で差分がわかるような設計書の作り方にしたほうがいいかもしれません。あるいは Git の差分を見て AI 自身が判断するようがよいのかも。実際、コードの差分などは Git diff を取ることが多いので。

お気に入り画面の背景色がちょっとミスっているのはさておき、ほぼ Claude Code で作ったコードと同じものができます。これで、概要設計やUI設計が「再現性がある」という状態ということが確認できます。

AI を含めたスパイラル開発

仕様駆動開発の場合には、要件定義や設計を煮詰めるところから始まるのですが、プロトタイプをもとにしたスパイラル開発の場合は、最初は「数行のプロンプト」で描けるような所謂バイブコーディングでも構いません、というスタンスになります。ただし、そこでできあがるのは、あくまで「動作確認」のコードなので、そこからいったん「設計生成」を生成した後に AI によるコード生成をします。

こうすると、設計からコードへの再現性を確認できるし、バイブコーディング的な AI コード生成の動作確認も可能になるのでは?と思うのですがどうでしょうか。

このスパイラル開発のループに「自動テスト」を入れることになります。テストコードとしては

  • 動作確認を自動化するための自動実行コード
  • 設計から生成されたコードを確認するためのテストコード
  • 完成品の品質を確認するためのテストコード

などが考えられます。

これを次から組み入れていきます。毎度、Android Studio でビルドした後に、チャットメッセージの動作確認を手作業でやっているのですが、これが意外と面倒です。できれば自動化したいですよね。果たして自動化できるのでしょうか?

コード

https://github.com/moonmile/BLE-chat

  • branch: dev/add-favorites-claude : お気に入り画面を Claude Code で 追加
  • branch: dev/add-favorites-copilot : 更新した設計書から GitHub Copilot でコード生成
カテゴリー: 開発 | コメントする

UI 設計書に従って、BLE-chat ツールに画面を追加

再びコードをもとに戻して、BLE-chat ツールに「設定」と「著作権表示」の画面を追加してみます。
あらかじめ、UI 設計書を用意して、これで一気に追加できるかを試していきます。

Claude Code で UI 設計書通りに追加する

UI 設計にしても、後から画面を追加することはよくあるわけで、そういうときに最初に用意した仕様書だけでなく、後付けができるほうが便利です。これもコードが多くなるとどうなるかわかりませんが。

暫くすると、設定画面等が再現できます。

この場合、UI 設計書通りになるので、何度やっても同じ画面が生成されるはずです。プロンプトの場合は、同じプロンプトを入れても微妙に違う画面になることが多いのですが、UI 設計書があればそれに沿った画面が再現できるというわけです。

同じパターンで、設定や著作権表示の画面以外のログ出力やデバッグ設定などの画面も作成できるようになるでしょう。

GitHub Copilot で UI 設計書通りに追加する

同じことを GitHub Copilot でもやってみます。

agents/UI設計書_設定_著作権.md に従って画面を追加して。

実に Claude Code と同じ画面ができあがります…が、右上の歯車ボタンが「・・・」になっているのが、ちょっと違いますね。画面遷移自体は「設定」→「著作権表示」となっているのですが。

このあたり UI 設計にある

## 2. 画面遷移

```mermaid
flowchart LR
    A[チャット画面] -->|メニューから設定| B[設定画面]
    B -->|戻る| A
    B -->|著作権情報| C[著作権情報画面]
    C -->|戻る| B
    B -->|送信者ID編集| D[送信者ID編集ダイアログ]
    D -->|保存| B
    D -->|キャンセル| B

の部分で「メニューからの設定」の解釈の違いっぽいです。

考察

このように後から UI 設計書を追加して、それに従って既存のコードに画面を追加することは可能です。プロンプトでちまちま追加するりも、設計書.md のようにまとめて AI に渡していたほうが効率的なのは確かです。

が、じゃあ、UI 設計書はどこまで作ればいいのか?という問題があります。今回のように、数行のプロンプトで作った場合、Claude Code と GitHub Copilot では異なる画面ができてしまいます。個人開発ならばそれでもいいのですが、会社のシステムのような場合はこれでは困ります。となると、なんらかの画面設計書を作った上で、その画面フォーマットンに合わせて、どちらの AI を使っても同じ画面できるのが望ましいのです。

さて、ここで作成している「UI設計書_設定_著作権.md」は、Claude Code で作成した画面を元にして、GitHub Copilot が逆作成したものです。

本来ならば、

1. 画面設計書を作成する
2. 画面設計書に従って、Cluade Code あるいは GitHub Copilot で画面を作成する

という2手順になるところですが、

1. 数行のプロンプトで Claude Code で画面コードを作成する
2. 画面コードから、GitHub Copilot で画面設計書を作成する
3. 画面設計書に従って、Cluade Code あるいは GitHub Copilot で画面を作成する

という3手順になっています。

前者のほうは、ウォーターフォール開発のようなもので、設計からコードという一方向の流れがあります。ある意味で、仕様書駆動開発も似たようなものです。流れが一方向になってしまうので、初手である画面設計書をしっかり作り込むことが必要になってきます。

後者のほうは、アジャイル開発…というよりも、スパイラル開発やプロトタイプ開発に近いです。最初にざっくりとした動作する画面を作っておいて、実際に動かしながら再び設計書を見直しあるいは追加していきます。

従来型のスパイラル開発の場合は、スパンを1か月程度にする場合が多く(アジャイルのスプリントとは違って、設計書からコード作成、テストまでを通すので)、このようなやり方は時間が掛かってしまうのですが、AI を使うことでコード生成部分や設計書作成部分を大幅に短縮できます。

1. 最初のスパイラルとして、数行のプロンプトを書く
2. 画面コードを生成する
3. (ここではないが、実際にテストを実行する)
4. 画面の動作を確認する
5. 2回目のスパイラルとして、画面コードから画面設計書を生成する
6. (ここではないが、画面設計書を見直す)
7. 画面設計書に従って、画面コードを生成する
8. (ここではないが、再びテストを実行する)

というイテレーション開発のサイクル廻っていると考えられます。

これを人間の開発者やるならば、2 と 7 の部分で時間が掛かってしまってやりきれないのですが、この部分を AI に任せてしまいます。そうなると、スパイラル開発のサイクルを高速に回す≒スパイラルの期間を短縮することが可能ですよね。

また、画面設計書も、数行のプロンプトを元にしてコード生成した後に逆生成することにすれば、追加機能や追加画面を作るときの設計の作成の手間も省力化できるかもしれません。
これは、3回目のスパイラルをいれて試してみます。

コード

https://github.com/moonmile/BLE-chat

  • branch: dev/add-setting-ui-claude : 設計書から Claude Code で 追加
  • branch: dev/add-setting-ui-copilot : 設計書から GitHub Copilot で 追加


カテゴリー: 開発 | コメントする

AI コード生成した BLE5-chat ツールを整備(設定画面の追加)

Claude Code である程度完成してしまったコードを、少しずつ整備していいきたい(あるいは機能を追加していきたい)としたとき、開発プロジェクトとしては、

  • 細かいテストをどうするのか?
  • 機能追加をしたときの、テストをどうするのか?
  • コードレビューは必要か?

の視点で「開発手法」を考えていく必要がある…と思う訳ですが、いざ、AI 時代のコード修正なるものを考えてみると、何から手を付けてよいものやらという感じになってしまったので、ここは、試行錯誤的に「何か手を付けてみて、何らかの障害にぶち当たるまで試してみる」という形でやってみます。

初期の画面に「設定」と「コピーライト」を追加する

BLE-Chat ツールでは、受信したメッセージと送信する入力欄がある1枚だけ(MainActivity.kt)で簡潔しています。これに「設定」と「コピーライト」を追加してみます。

このあたり、設定用の画面とコピーライト用の画面を設計するところなのですが、このあたりの定番の画面ならば雑に追加しても大丈夫であろうということで、agents/ に .md ファイルを作るのではなく、プロンプトで指定してみます。Android アプリの画面の追加は FolkBears で経験済みなので、そこそこうまく追加してくれるはずです。

Claude Code のプロンプトで追加

Claude Code のプロンプトで雑に指定しいます。

「設定」画面と「コピーライト」画面に追加して。

この場合、定番の方式で追加するので、AI がスタンダードで考える画面の構成で追加してくれます。と言いますか、定番の方法で追加してしまいます。

  • SettingsScreen.kt
  • CopyrightScreen.kt

ファイル名や画面構成は、AI が適当に決めるので、それに従います。逆に言えば、社内の標準フォーマットとかコード規約などを含めることが難しいです。個人開発やツール程度ならばいいけれど、なんらかの標準にあわせる場合には、Agents に .md ファイルで規定を作るか、サンプル用の画面を作るほうがよいでしょうね。

左上に歯車のボタンを付けて、「設定」→「著作権情報」という流れで画面遷移させます。

設定の画面では「送信者ID」や「送信継続時間」などがあるので、これをアプリから固有のもににするのか、ユーザーが設定できるようにするのか、などを開発時に考える必要がでてきます。

送信者IDは変更できるようになっているのですが、実機で試してみます。

SettingsItem(
    label = "送信者ID",
    value = selfId,
    trailingIcon = {
        IconButton(onClick = { showEditDialog = true }) {
            Icon(
                imageVector = Icons.Default.Edit,
                contentDescription = "編集",
                tint = MaterialTheme.colorScheme.primary
            )
        }
    }
)

初期値の「21e4c75c」から変更後の「11112222」に変わっているので大丈夫そうです。

// SharedPreferences から selfId を読み込み、Flow で保持
private val selfIdFlow: MutableStateFlow<String> by lazy {
    val stored = prefs.getString("selfId", null)
        ?: UUID.randomUUID().toString().take(8).also { saveSelfId(it) }
    MutableStateFlow(stored)
}

送信者IDは、MainActivity#selfIdFlow として保持されているので、文字列8文字であれば何でもいいのですが、このあたりは、設計書として書くか、AI コーディングのままとするとかで判断の迷うところです。チャットツールとしては、ニックネームを自分でつけたいところなので、8文字の制限はきついかもしれません。

この他に、BLE の設定値として、送信継続時間や重複排除 TTLなどがありますが、これは SettingsScreen.kt に直が気になっています。

// ── BLE 設定セクション ─────────────────────────────────
SettingsSectionHeader(title = "BLE 設定")

SettingsItem(
    label = "送信継続時間",
    value = "10 秒(固定)"
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))

SettingsItem(
    label = "重複排除 TTL",
    value = "30 秒(固定)"
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))

SettingsItem(
    label = "参加者タイムアウト",
    value = "60 秒(固定)"
)

このあたりの値は、build.gradle.kts に定数として定義しておいて、SettingsScreen.kt 画面で表示させるほうがよいので、これを変更してみましょう。

送信継続時間、重複排除 TTL、参加者タイムアウト の秒数を、build.gradle.kts に定数として定義できるように変更して。それらの値を使って、SettingsScreen.kt などの関連するコードを修正して。

いわゆるリファクタリングで、初期の AI コーディングでマジックナンバー化してしまったものを、環境変数などの設定値として外出しにします。

送信継続時間などは、BLE の受信待ちなどで使っているので、それも修正してもらいます。

defaultConfig {
    applicationId = "net.moonmile.ble5_chat.claude"
    minSdk = 26
    targetSdk = 36
    versionCode = 1
    versionName = "1.0"

    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

    // BLE タイミング定数(秒単位)
    buildConfigField("int", "ADVERTISE_DURATION_SEC",   "10")
    buildConfigField("int", "DUPLICATE_FILTER_TTL_SEC", "30")
    buildConfigField("int", "PEER_TIMEOUT_SEC",         "60")
}

ADVERTISE_DURATION_SEC などが追加されています。

SettingsScreen.kt では、これらの値を BuildConfig から読み込んで表示するようになっています。

// ── BLE 設定セクション ─────────────────────────────────
SettingsSectionHeader(title = "BLE 設定")

SettingsItem(
    label = "送信継続時間",
    value = "${BuildConfig.ADVERTISE_DURATION_SEC} 秒"
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))

SettingsItem(
    label = "重複排除 TTL",
    value = "${BuildConfig.DUPLICATE_FILTER_TTL_SEC} 秒"
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))

SettingsItem(
    label = "参加者タイムアウト",
    value = "${BuildConfig.PEER_TIMEOUT_SEC} 秒"
)
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))

おそらく、こんな風なあまりテストの必要ない画面ならば AI へのプロンプトの指示で十分です。その後に、ビルド&実行して、チャットを使って修正指示を出してもそれほど手間ではないでしょう。
実のところ「送信継続時間」が本当に、その時間で停止しているのかなどの動作チェックが必要なのですが、これはまた別の形でテストをします。

GitHub Copilot のプロンプトで追加

コードを元に戻して、GitHub Copilot のプロンプトで同じことをやってみます。

いちからのコード生成は失敗してしまったので、Claude Code で作成したコードをベースに GitHub Copilot を使って追加する形にします。

「設定」画面と「コピーライト」画面に追加して。

GitHub Copilot の場合は、歯車ではなくて、右上の「︙」のメニューから「設定」と「コピーライト」を選ぶ形になりました。これも定番の方法で、AI が考える画面の構成で追加してくれます。

折角なので、設定画面に

  • 送信継続時間(ブロードキャストしている継続時間)
  • 重複排除 TTL(同じ送信者IDのメッセージを重複とみなす時間)
  • 参加者タイムアウト(受信したメッセージの送信者を参加者とみなす時間)

を追加してもらいましょう。

Copilot のほうは、あらかじめ定義されていた BleAdvertiserManager.ADVERTISE_DURATION_MS などから値を拾って表示するようになっています。

SettingRow(
    title = "送信継続時間",
    value = BleAdvertiserManager.DEFAULT_ADVERTISE_DURATION_MS.toSecondsLabel()
)
HorizontalDivider()
SettingRow(
    title = "重複排除 TTL",
    value = DuplicateFilter.DEFAULT_TTL_MS.toSecondsLabel()
)
HorizontalDivider()
SettingRow(
    title = "参加者タイムアウト",
    value = PeerRegistry.DEFAULT_TIMEOUT_MS.toSecondsLabel()
)
HorizontalDivider()

送信者IDが編集できないので、これを追加します。

送信者IDを編集できるようにして。

最後に定数を build.gradle.kts に移動して、SettingsScreen.kt で表示するように変更します。

– 送信継続時間、重複排除 TTL、参加者タイムアウト の秒数を、build.gradle.kts に定数として定義できるように変更して。それらの値を使って、SettingsScreen.kt などの関連するコードを修正して。

同じパターンで build.gradle.kts で定数を定義できるように変更されます。

defaultConfig {
    applicationId = "net.moonmile.ble5_chat.claude"
    minSdk = 26
    targetSdk = 36
    versionCode = 1
    versionName = "1.0"
    buildConfigField("long", "ADVERTISE_DURATION_MS", "10000L")
    buildConfigField("long", "DUPLICATE_FILTER_TTL_MS", "30000L")
    buildConfigField("long", "PEER_TIMEOUT_MS", "60000L")

    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

比較

Copilot のほうはちょっと手間取ったのですが、Claude Code とほぼ同じ画面が作成できます。
おそらく、それぞれのモデルの中にある典型的な設定画面をピックアップしたと思うです。
ただし、これは AI が好き勝手に作った画面なので、ならからのデザインの統一をするときにはこの方式では困ります。
これを agents/ に UI 設計として保存しておき、狙った形で設定画面と著作権表示の画面を作成できるようにしてみます。

この場合、2つの方式があって、

  • 機能を作ってから後から画面のデザインだけ合わせる
  • 最初の画面のデザインを決めておいて、後から機能を追加していく

ということが考えられます。

設定画面の UI 設計書を作成する

さて、どのように AI に渡す UI 設計書を作るかという問題です。

この場合、既にできあがった「設定」と「著作権表示」の画面から、UI 設計書を作って貰うことにします。

SettingsScreen.kt, CopyrightScreen.kt から、UI 設計書を作成して。

Figma などの UI デザインツールを使って UI 設計書を作る方法もあるのですが、今回のような定番の画面であれば「AI が読み取れるような UI 設計書を AI 自身に作って貰う」方が手っ取り早いでしょう。

agents/UI設計書_設定_著作権.md

# UI設計書: 設定画面 / 著作権情報画面

## 1. 対象画面

- 設定画面
  - 実装: `SettingsScreen`
  - ファイル: `src/app/src/main/java/net/moonmile/ble5_chat/claude/ui/SettingsScreen.kt`
- 著作権情報画面
  - 実装: `CopyrightScreen`
  - ファイル: `src/app/src/main/java/net/moonmile/ble5_chat/claude/ui/CopyrightScreen.kt`

## 2. 画面遷移

```mermaid
flowchart LR
    A[チャット画面] -->|メニューから設定| B[設定画面]
    B -->|戻る| A
    B -->|著作権情報| C[著作権情報画面]
    C -->|戻る| B
    B -->|送信者ID編集| D[送信者ID編集ダイアログ]
    D -->|保存| B
    D -->|キャンセル| B
```

## 3. 設定画面

### 3.1 目的

- 端末設定として送信者IDを表示・編集する
- BLE動作に関する設定値を参照表示する
- アプリ情報への導線を提供する

### 3.2 レイアウト構成

- 画面全体は `Scaffold`
- 上部に `TopAppBar`
- 本文は `Column` + `verticalScroll`
- セクションごとに見出しと項目を配置
- 項目間は `HorizontalDivider` で区切る

次は、この UI 設計書をもとに、AI コーディングで「設定」画面と「著作権表示」画面を作成してみます。

コード

https://github.com/moonmile/BLE-chat

  • branch: dev/add-setting-page : Claude Code で 追加
  • branch: dev/add-setting-page-copilot : GitHub Copilot で 追加


カテゴリー: 開発 | コメントする

BLE5-chat ツールを Claude Code で作る

せっかくなので、同じ「概要設計.md」を使って Claude Code で作ってみることにします。

実は、1回目は失敗して、やり直しています。

  • settings.gradle.kts の includeGroupByRegex でエラー発生
  • app/build.gradle.kts の plugins の alias でエラー発生

どちらもファイル構成がわかっていれば、手で直せばいいような感じなのですが、Android Studio を使えない前提≒あまりコードを知らなくて Android アプリが作れるという前提でやるので、もういちど最初から作り直しています。
作り直しても、15 分ぐらいでできあがります。

逆に言えば、初手のところでうまくいかなかった場合は、AI コーディングをやり直したほうが手っ取り早いです。
夜間バッチ的に動かす場合は、課金の問題もあるので手作業での修正が安価かもしれませんが、この程度のアプリ作成ならば何回かやりなおしたほうがよさそうです。

結果は、作成できました!

GitHub Copilot では途中で詰まってしまいましたが、Claude Code の場合は作成できました。


記事的には [Core Bluetooth] アドバタイジングデータを最大255バイトに拡張するAdvertising Extensions https://zenn.dev/shu223/articles/advertising-extensions の話で、この記事では iOS の拡張アドバタイズなので受信のみとなりますが、Android の場合は送受信が可能です。最大 255 バイト程度のデータがブロードキャストできます(iBeacon は 32 バイトです)。

そんな訳で、コネクション無しの状態で、複数の Android 端末に一気にメッセージを送信できます。コネクションしないので、数十台の端末が集まったとしてもメッセージの送受信に負担が掛からないのですが…まあ、数十台は集まりませんね。実際のところは、4,5台ぐらいでしょう。

基本的な送受信の仕組みは iBeacon の送受信と変わらないので、メッセージデータのフォーマットさえあわせてやれば、こんな感じでチャットツールができる。というところまでは確認できました。

さて、思った通りに動作しているか、をどう確認する?

いわゆる動作確認をした後に、細かい動作のチェックをしていきます。
最終的には公開してアプリとして利用できることを考えると、単に「動いた」だけでは不十分です。

  • 細かいテストをどうするのか?
  • 機能追加をしたときの、テストをどうするのか?
  • コードレビューは必要か?

クラス設計を固定化するために、動作確認のためのテストコードを書くとよいだろう。
というのは引き続き。

コード

https://github.com/moonmile/BLE-chat

カテゴリー: 開発 | コメントする

BLE5-chat ツールを GitHub Copilot で作る…が、途中で断念

「概要設計.md」ができあがったので、GitHub Copilot を使って実装をしていきます。Android Studio を立ち上げて Kotlin のプロジェクトをひな型として作成した後に、AI コーディングを進めるとよいです。

たぶん、Claude Code などのバッチ型はプロジェクト作成まで skills などで用意されているのですが、Web アプリ以外はちょっと難しいか…という感じで、Copilot の場合はよく失敗したので、手作業でプロジェクトだけは作っておきます。

ブランチを切る

AI ペアプロをする場合には、この時点でブランチを作成しておきます。
Claude Code の場合は自動でブランチを作ってくれるのですが、AI ペアプロの場合は、人間のほうで適当なタイミングで作成しておくとよいです。いつも通り、タスクやチケットを単位で作るような感じでよいです。

最初の AI コーディング

  • 概要設計.md に従って src/copilot に実装して。

Claude Code の場合も試したいので GitHub Copilot では src/copilot に実装していきます。

ファイルを見ている限り、なんとか AI コーディングが終わった感じですが、残念ながらそのままでは動きません。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.BLE5chat">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.BLE5chat">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
  • AndroidManifest.xml に BLE を利用する権限を追加して。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

    <uses-permission
        android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />
    <uses-permission
        android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

BLE の権限の設定はできたとして、Android Studio でビルド&実機で実行してみます。

これを見ると、

  • ステータスバーが重なってしまっている
  • 入力画面に「Bluetooth をオンにしてください」と表示されている

初動までを確認する

  • 実機で BLE を ON にしても「Bluetooth をオンにしてください」がでます。
    調査して。

結果として以下が追加で実装されます。

    // Bluetooth アダプタ状態監視用 BroadcastReceiver
    private val bleStateReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            if (intent?.action != BluetoothAdapter.ACTION_STATE_CHANGED) return
            val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
            AppLogger.debug("BLE adapter state changed: $state")
            lifecycleScope.launch {
                when (state) {
                    BluetoothAdapter.STATE_ON -> {
                        AppLogger.info("Bluetooth turned ON")
                        _uiState.value = _uiState.value.copy(
                            canSend = true,
                            isScanning = true,
                            errorMessage = null
                        )
                    }
                    BluetoothAdapter.STATE_TURNING_OFF,
                    BluetoothAdapter.STATE_OFF -> {
                        AppLogger.info("Bluetooth turned OFF (state=$state)")
                        _uiEffects.emit(ChatUiEffect.NotifyBleDisabled)
                    }
                }
            }
        }
    }

これで一応入力ができるようになるのですが、実は「概要設計.md」には、BleChatService の責務として書いてあります。

#### BleChatService
- 責務
	- `BleAdvertiserManager` と `BleScannerManager` を統合
	- BLE 権限・Adapter 状態を監視
	- アプリの開始/停止に追従して送受信を制御
	- `BroadcastReceiver` で `ACTION_STATE_CHANGED` を受信し、`STATE_TURNING_OFF` / `STATE_OFF` を検知したら送受信を即時中断して `ErrorHandler` へ委譲する
	- BLE OFF 検知時は `ChatUiState.canSend = false` と `ChatUiEffect.ShowToast` を上位へ通知する
- 主なメソッド
	- `start()`
	- `stop()`
	- `send(chatMessage: ChatMessage)`
	- `incomingMessages(): Flow<ChatMessage>`
	- `onBleAdapterStateChanged(state: Int)` ※ BroadcastReceiver から呼ばれる内部ハンドラ

元のコード内にも BleChatService.start(), BleChatService.stop() として使われているので、これ活かす形に書き変えて貰います。

    private fun handleStartChat() {
        lifecycleScope.launch {
            try {
                AppLogger.info("Starting chat")
                _uiState.value = _uiState.value.copy(canSend = true, isScanning = true)
                // TODO: BleChatService.start()
            } catch (e: Exception) {
                AppLogger.error("Failed to start chat", e)
                _uiEffects.emit(ChatUiEffect.ShowToast("チャット開始に失敗しました"))
            }
        }
    }

    private fun handleStopChat() {
        lifecycleScope.launch {
            try {
                AppLogger.info("Stopping chat")
                _uiState.value = _uiState.value.copy(canSend = false, isScanning = false)
                // TODO: BleChatService.stop()
            } catch (e: Exception) {
                AppLogger.error("Failed to stop chat", e)
                _uiEffects.emit(ChatUiEffect.ShowToast("チャット停止に失敗しました"))
            }
        }
    }

実装を設計にあわせる

  • BroadcastReceiver による動的監視の部分を、概要設計.md 通りに、BleChatService を実装して利用できますか?

これで BleChatServiceImpl.kt が実装されます。このあたり、GitHub Copilot が設計を忘れいるのか、それとも Claude Code ならば大丈夫なのか、という問題がありますが、少なくとも「必ずしも設計通りにコーディングをしないこともある」というのが重要です。
今後はどうなるのかわかりませんが、なんらかの形で「設計書通りにコーディングする」というスタイルを促すようなプロンプトの工夫が必要になると思われます。

あるいは、設計書通りにコーディングされているかをチェックするテストコードを追加することになります。

受信状態を実機で確認する

受信状態を実機で確認してみますが、受信人数が 0 人のままになります。かつ、メッセージを送信しても受信ができていません。

  • 複数の端末を起動しても 0 人のままになります。

この手の実機動作確認が AI コーディングの一番の難所になります。

AI としては直しているつもりなのですが、まだ動かないようなので、動作ログを入れて貰います。

  • peerCount が常に 0 になっています。送受信のところにログを入れて、動作確認できるようにして。

ログをチャットに貼り付けて、分析してもらいます。

原因として ScanFilter.setServiceUuid() がうまく働いていない可能性があるとのことなので、ScanFilter を AI が外してみます。

ログは出るようになったのですが、まだ受信できません。

いったん保留

そもそも、手元の Android 端末が BLE5 Extended Advertising を通しているか不明なので、ちょっと方式変えて、疎通試験用のライブラリを作っていくことにします。

カテゴリー: 開発 | コメントする

BLE5 の Extended Advertising を使って AI コーディングを試していく

ちょっと、執筆と新人研修が重なりあってしまったので、ブログの更新が滞ってしまっているのですが、ぼちぼち再開します。
コーディング的には ExcelUnoLike の続きをしたいところですが、どうもあれこれと設計を見直したくなって重たくなりそうなので、先に BLE5 を使ったツールのほうを試していきます。

プログラマから見た AI コーディングの懸念

普段使いのコーディングでは VSCode + GitHub Copilot を使って「AI ペアプロ」という形で進めていきます。AI ペアプロなので、常にプログラマが並走しています。AI に任せっきりというわけでもなく、AI が出力してきたコードを鵜呑みするわけでもなく、ある程度ですが AI コードを常にレビューしながら進めていきます。まあ、いろいろ面倒になって、ちょっとだけ動作確認した後に Git にコミットしてしまうことも多いのですが、場合によっては 1 日分の AI コードを戻してしまうことも多いです。

BLE 絡みと Web API 絡みでプロトタイプ的なコードを結構書いてきたのですが、それ以上の品質に AI コーディングが辿り着くのか?というと、現状では何とも言えません。このあたりは、私が「AI ペアプロ」に留まっている理由でもあり、巷のテクニカルな記事をみると大規模で手間のかかる AI コーディングならば良さそうなのですが、こう一歩ずつ試してくようなプロトタイプ型のコーディングではやっぱり「AI ペアプロ」のほうがよさそうな気がします。

とはいえ、手元で「Claude Code」を入れて(実際には Claude Cowork 用に入れたのですが)試してみると、ああ、確かに Web アプリケーションならば、仕様書を与えてコーディングさせることは可能っぽいです。というか、逆に言えば「AI ペアプロ」的な使い方は Claude Code だとやり辛いです。逆に言えば、夜間に一括でできるようなバッチ的な処理には非常に向いています。おそらく、単体テストの自動化と再帰的なコード修正がやり辛いのは、ここのバッチ的な処理が原因かもしれません。

という訳で、ここまでが愚痴…

AI ペアプロ型とバッチ型の両方を試してみる

恐らく一長一短があると思うのですが、

  • VSCode + GitHub Copilot を使った「AI ペアプロ」型
  • Claude Code を使った「バッチ型」

の両方を試してみます。巷のブログ記事だと、「Web サイトが一瞬でできました」タイプが多いので、仕様が決まったら一気に作る Claude Code のほうが向いているような気もしますが、そのあたりの非 Web アプリの場合はどうなのか?というお試しです。

本来ならば要件定義からスタートしたほうがいいのですが、今回は機能要件レベルからスタートしてみます。機能要件をまとめるときの諸々は Copilot などの Chat ツールを使っています。

## 機能要件

- BLE5 の Extended Advertising を利用して、Android 端末同士でチャットができる
- チャットの内容は、テキストメッセージとする
- iBeacon のようにブロードキャストでメッセージを送信し、受信側はそれを受け取って表示する
- BLE5 通信の届く範囲内であれば、複数の端末が同時にチャットに参加できる

チャットツールなんて「チャットツールを作って」と AI に頼めばさっくりと作ってくれる典型的なパターンなのですが、今回は WEB サーバーを使わないし、Firebase も使わないパターンです。しかも、Android 端末同士限定で、BLE5 の Extended Advertising を使います。Extended Advertising 形式だと、iBeacon のようにブロードキャスト通信ができます。実運用的にはコネクション型を使ったほうがいいのですが、このあたりは趣味的に BLE5 を使ってみることにします。

このあたり、AI にとって難関なのは「物理的な Android を動かさないといけない」というパターンです。これ、バッチ型のほうに非常に不利なのですが、なんとかクリアできるように考えていきたいです。実は AI ペアプロ型の場合も問題が多くて、テストをするときに「AI が提案するコードを人間が動かし続ける」という使役的なパターンが何度となく続きます。確かに、ペアプロなのですから、コード役とドライビング役が交代してもいいのですが、なかなかこれは辛いです。FolkBears の改修しているときに何度となく発生しています。同じパターンは、Web API を作成したときの React での UI のプロトタイプでも発生します。プロトタイプ的に作る場合にはどこかで諦めればいいのですが、製品として品質を上げる場合にはどこまでやるのかが不明になってしまいます。
ここを自分的には何とかしたいところですね。

このあたり、

  • テスト工程を含めて、これも自動化できるように考える
  • 例えば、ログ取得など、単体テスト、結合テストを AI 任せにできるだろうか?

という課題も含めていきます。
クリアすべきテスト項目も AI ペアプロ的にあるいはバッチ的に追加します。

設計する

ここ 1 年ほど AI コーディング(バイブコーディングも含めて)を試してきた結果、経験上、仕事で使う場合には軽く「設計」をしておくと方向性が定まってよい、と結論に至っています。仕様書駆動(spec駆動)ほど詳細に書くわけでもなく、先の「機能要件」ほど大雑把なままではなく、という中間的な位置です。
結局のところ、ウォーターフォール開発にせよアジャイル開発にせよ、途中での変更や見直しからは逃れられないので、そのあたりの余地&人的な手間を考慮する必要がでてきます。
特に、単独でのAI コーディングの場合には、伝達コスト(コミュニケーションコスト)を抑えることができるので、派手な設計書は必要ありません。まあ、多人数での開発の場合には、レビューなどが必要となるのである程度の文書化が必要になるのですが…今回は Android アプリなので小規模なものを対象とします。

実は、機能要件を渡せば VSCode + GitHub Copilot の組み合わせでも結構なところまでできてしまいます。UI なり、メッセージ形式なり、通信フローなりを、既存のものからうまく引っ張ってくるので、ある程度動くものが素早くできあがります。なので、「プロトタイプ的に」作るのは、機能要件だけでも結構 ok です。
ただし、最近の技術ブログや SNS でも話題になっているように、これをどうやってテストしていくのか、将来機能追加があるときにどうやって品質を保証していくのか、という問題がでてきます。このあたりは、開発するアプリ/製品の寿命や運用形態にもよるので、その都度考える必要がでてくるのですが、少なくとも AI コーディングで機能要件だけ渡して、一気に作って貰って放置するのはダメです。
なんらかの設計方式を与えてしまったほうがコードのスタイルが揃い、後での修正や機能追加がやりやすくなります。

クラス設計

  • 機能要件を満たすように、クラス設計を書いて。

とプロンプトを打って、クラス設計を出力させます。

## クラス設計

BLE Extended Advertising を使った「ブロードキャスト型チャット」を成立させるため、責務を以下のように分離する。

### 1. クラス一覧

| クラス名 | 種別 | 主な責務 |
|---|---|---|
| ChatActivity | UI | メッセージ一覧表示、送信入力、画面イベント処理 |
| ChatViewModel | Presentation | UI 状態管理、送信要求、受信イベント購読 |
| ChatRepository | Domain/Application | 送受信ユースケース統合、履歴の仲介 |
| BleChatService | BLE Facade | BLE 初期化、Advertiser/Scanner の開始停止管理 |
| BleAdvertiserManager | BLE Tx | Extended Advertising による送信データの分割・送出 |
| BleScannerManager | BLE Rx | 広告パケット受信、再構成、重複排除 |
| PacketAssembler | Protocol | 受信パケットの順序復元、メッセージ復元 |
| PacketFragmenter | Protocol | テキストメッセージの分割、ヘッダ付与 |
| MessageCodec | Protocol | 文字列とバイト列の変換、バージョン管理 |
| CryptoManager | Security | 暗号化/復号(任意有効化)、鍵管理窓口 |
| DuplicateFilter | Reliability | MessageId による重複受信抑止 |
| PeerRegistry | Session | 参加端末の最終受信時刻管理(参加者把握) |
| ChatMessage | Model | チャット本文、送信者 ID、時刻、MessageId |
| AdvPacket | Model | 1広告単位データ(MessageId、Seq、Total、Payload) |
| AppLogger | Cross-cutting | ログ収集(テスト・障害解析用) |
| ErrorHandler | Cross-cutting | BLE 例外、権限エラー、復旧リトライ判断 |


### 2. 主要クラス詳細

#### ChatActivity
- 責務
- メッセージ一覧表示
- 送信ボタン押下イベントを ViewModel に委譲
- BLE/権限状態の表示
- 主なメソッド
- `onCreate()`
- `observeUiState()`
- `onSendClicked(text: String)`

このクラス設計は最初のたたき台になるものなので、中身を確認していきます。この中身は、適当なクラス設計のテンプレート文書があればそれにフォーマットを合わせてくれます。プランモードで慎重にしてもいいのですが、AI ペアプロ型の場合は、いったん設計の部分で止めておくほうが無難です。まあ、Android & Kotlin のプロジェクトになじみがないとか、BLE 通信のところに知見がないと、設計段階で戸惑ってしまうのですが、今回はある程度 Android アプリを作ったことがあるという前提になります。

今回は、初期型として作りたいので、チャットの返答やクラス設計のサマリを見ていくつかの機能を削っておきます。

  • 暗号化 CryptoManager の機能は削除して。
  • Kotlin の Jetpack Compose を使う場合、ChatViewModel は必要?
  • Compose を利用する前提にして、テストしやすい方式に変更して。
  • 回転 ChatRoute は考えずに、縦置き(ポートレート)の画面構成にして。
  • メッセージは 100 文字以内とするため、PacketAssembler と PacketFragmenter を削除して。
  • ブロードキャストするメッセージは10秒だけ送信する。

大まかなクラス設計ができたら、シーケンス図を使って確認します。

シーケンス図

1年前の FolkBears の改修のときにはフローチャートを手作業で書くしかなかったのですが、いまだと Mermaid 形式で書いてくれます。

  • 主な送受信のフローを、シーケンス図に追加して。
## シーケンス図

### 1. メッセージ送信

mermaid
sequenceDiagram
actor User as User
participant Screen as ChatScreen
participant Activity as MainActivity
participant Repo as ChatRepository
participant Service as BleChatService
participant Codec as MessageCodec
participant Advertiser as BleAdvertiserManager

User->>Screen: メッセージ入力 / 送信押下
Screen->>Activity: onSend(text)
Activity->>Repo: publishMessage(ChatMessage)
Repo->>Service: send(chatMessage)
Service->>Codec: encode(chatMessage.text)
Codec-->>Service: payload
Service->>Advertiser: broadcast(AdvPacket)
Advertiser->>Advertiser: startAdvertising()
Advertiser-->>User: Extended Advertising で送信開始
Advertiser->>Advertiser: 10秒タイマー開始
Advertiser->>Advertiser: stopAdvertising()
Advertiser-->>User: 送信停止
  • メッセージ送信時に BLE OFF にした時の動作を追加。
  • メッセージ受信時に BLE OFF を追加。
  • いくつかのエラーを処理を追加しておきます。

この時点で、「agents\概要設計.md」ファイルは、300 行を超え始めているので、きちんとチェックするにはちょっと手間な量になってきています。シーケンス図やクラス図は、設計の正しさをチェックするためなので、AI コーディングの場合はチェックした後は削除してもよいかもしれません。人間だけでチェックする場合は、設計書として残さないとコーディングのときに困ってしまうのですが、AI コーディングの場合はあまり関係ありません…というか、シーケンス図通りに AI がコーディングするとは限らないという意味ですね。

FolkBears の改修のときは AI にコードからシーケンス図を作って貰ったので、コードを吐き出してからシーケンスをチェックしてもよいかもしれません。

軽く UI 設計をしておく

実際のところは Figma とか使って UI デザインをしたほうがいいのですが、今回はプロトタイプ的に作るので、テキストベースで UI 設計をしてもらいます。コードとしては Jetpack Compose で書いてもらうことにします。

## UI 設計

### 1. 画面レイアウト(ポートレート固定)

画面を上から 3 つの固定領域で構成する。ヘッダと入力領域は固定高さ、メッセージ領域が残りの高さをすべて占有する。

┌─────────────────────────────────────┐
│ ヘッダ(固定) │ ← StatusBar 直下
│ [● BLE 状態] [👥 参加者数] [⟳ スキャン中] │
├─────────────────────────────────────┤
│ │
│ メッセージ一覧(可変・スクロール可) │ ← weight(1f) で拡張
│ │
│ ┌───────────────────────────────┐ │
│ │ HH:MM senderId │ │ ← 受信メッセージ(左寄せ)
│ │ メッセージ本文 │ │
│ └───────────────────────────────┘ │
│ │
│ ┌─────────────────────┐│
│ │ senderId HH:MM ││ ← 自分のメッセージ(右寄せ)
│ │ メッセージ本文 ││
│ └─────────────────────┘│
│ │
├─────────────────────────────────────┤
│ 入力領域(固定) │
│ [TextField(テキスト入力)] [送信ボタン] │
└─────────────────────────────────────┘

これもどこまでコードに反映されるか分からないのですが…正確にコードに反映されないのであれば、UI 設計としてはどこまで細かく指定できるのか?ということになりますね。

UI 設計は Claude Code に読み込ませる必要もあるため、Copilot で作ったものをそのまま貼り付けておきます。

ここで、おおまかな概要設計ができたので、これを GitHub Copilot に読み込ませてコーディングをしていきます。

カテゴリー: 開発 | コメントする

Google NotebookLM 即効活用大全 発売中

https://www.amazon.co.jp/dp/B0GGW3ZC1P

Google NotebookLM で手元の資料を活用しよう、という本です。

https://notebooklm.google.com は Google アカウントがあれば無料で使えるので、本書のサンプル文書を使ってお試しができます。一般的な解説書とは違って、サンプルの文書(PDFや画像ファイルなど)をダウンロードをして手元の NotebookLM で手を動かしながら試せます、という形式になっています。いわゆる、プログラムコードを Git からダウンロードして動作確認ができるタイプの本と思ってよいです。

基本機能は、前半に導入編、基本編と一通り説明があります。日々追加される NotebookLM の各機能について執筆時点での網羅をしています。

ただ、それだけど、どんどん進化してしまう NotebookLM 自体に書籍の内容が追いつきませんよね。それだけだと巷のブログや Note や動画などの解説で十分です。

この本で作りたかったのは、後半にある「ケーススタディ編」です。

NotebookLM に突っ込むサンプル文書を用意して、自分でプロンプトを呼び出して試すことができる、という形式にしてあります。サンプル文書も単にファイルや画像を検索するだけではなくて、実際ありそうな検索や文書をいれてあります。

これを会話形式で、シチュエーションを想定します。

その後に、フローチャート方式で、NotebookLM がどこで活用されるのか確認します。

なんとなく理解できたら、実際に NotebookLM にサンプル文書をいれて動かしてみましょう。というスタイルになっています。

まあ、実際のところは手を動かさなくてもケーススタディを読むだけでも雰囲気が伝わるようになっていますが、NotebookLM の基本技を組み合わせると、仕事やグループワークをするときにこんな風に使えますよ、というケースを34パターン用意してあります。

想定する職業はばらばらなので、自分の職種とは違うものが多いとは思うのですが、使い方のヒントにはなると思います。特に、手元に PDF やテキストデータ、Word ファイルなどで情報が文書として残っている場合は、お得に NotebookLM を活用できます。
ひとまず、NotebookLM で必要そうなファイルを突っ込んでみて、何か質問してみると、きっちりと PDF ファイルの内容を理解して回答してくれることがわかります。抜き出しだけではなくて、箇条書きでまとめたり表形式にフォーマットしなおしたりすることも可能です。

このあたり、以前 NotebookLM を使ってはみたけれど、いまいち使い方がわからなくてやめてしまった、という人に一番おすすめです。あと、NotebookLM ってのは聞いてはいるけど、いまいち何ができるのか分からない、という人にも二番目におすすめします。

カテゴリー: 開発 | コメントする