
techtekt アドベントカレンダー5日目の記事です。
はじめに
こんにちは、カスタマープロダクト本部プロダクト開発統括部グロース開発部の徳田です。
doda サイトの開発エンジニアを担当しています。
AI コーディングアシスタントの活用が広がる昨今、弊社でも Claude Code を導入し、日常の開発に AI を取り入れる取り組みを行っております。
一方で本記事では、業務利用の事例ではありません。
自分が普段から使い慣れている GPT-5.1 Codex をAI支援として開発フローへ取り入れたときに何がどこまでできるのかを、実装・テスト・ドキュメントの観点で整理して紹介します。
内容
本記事では Codex を「実際の開発フローでどこまで代替できるか」を見るために、以下の工程をプロンプトで指示します。
- 実装
- テストコード作成
- ドキュメント作成
アプリケーションの案件
Todo リストのシンプルな SPA の作成をします。
- Todo の追加/削除
- Todo の完了・未完了の切り替え
- localStorage を利用
技術スタック
- React
- TypeScript
- Vite
- Jest
- React Testing Library
実装
プロンプト
TodoListの作成をお願いします。
内容
・Todoの追加/削除
・Todoの完了・未完了の切り替え
・localStrageを利用
技術スタック
・React
・TypeScript
・Vite
・Jest
・React Testing Library
仕様書と起動までの手順書を含め、作成をお願いします。
ディレクトリ構造
src/
├── tests/
│ └── App.test.tsx
├── components/
│ └── TodoItem.tsx
├── hooks/
│ └── useTodos.ts
├── types/
│ └── todo.ts
├── App.tsx
├── index.css
├── main.tsx
└── setupTests.ts
画面・動作確認

テストコード
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from '../App';
type User = ReturnType;
describe('TodoList App', () => {
beforeEach(() => {
localStorage.clear();
});
const typeTodo = async (user: User, text: string) => {
const input = screen.getByLabelText('追加する Todo の内容');
await user.clear(input);
await user.type(input, text);
};
it('shows an empty state message when no todos exist', () => {
render( );
expect(
screen.getByText('まだタスクがありません。最初の Todo を追加してください。'),
).toBeInTheDocument();
});
it('adds a todo and clears the input field', async () => {
const user = userEvent.setup();
render( );
await typeTodo(user, 'React を勉強する');
await user.click(screen.getByRole('button', { name: '追加' }));
expect(screen.getByText('React を勉強する')).toBeInTheDocument();
expect(screen.getByLabelText('追加する Todo の内容')).toHaveValue('');
});
it('toggles the completion state of a todo', async () => {
const user = userEvent.setup();
render( );
await typeTodo(user, 'テストを書く');
await user.click(screen.getByRole('button', { name: '追加' }));
const checkbox = screen.getByRole('checkbox', { name: 'テストを書く の完了状態を切り替える' });
expect(checkbox).not.toBeChecked();
await user.click(checkbox);
expect(checkbox).toBeChecked();
});
it('deletes a todo item', async () => {
const user = userEvent.setup();
render( );
await typeTodo(user, '最初のタスク');
await user.click(screen.getByRole('button', { name: '追加' }));
await typeTodo(user, '次のタスク');
await user.click(screen.getByRole('button', { name: '追加' }));
const deleteButtons = screen.getAllByRole('button', { name: /削除/ });
await user.click(deleteButtons[0]);
expect(screen.queryByText('最初のタスク')).not.toBeInTheDocument();
expect(screen.getByText('次のタスク')).toBeInTheDocument();
});
it('persists todos to localStorage', async () => {
const user = userEvent.setup();
const { unmount } = render( );
await typeTodo(user, '永続化テスト');
await user.click(screen.getByRole('button', { name: '追加' }));
expect(JSON.parse(localStorage.getItem('todolist_codex.todos') ?? '[]')).toHaveLength(1);
unmount();
render( );
expect(screen.getByText('永続化テスト')).toBeInTheDocument();
});
});
ドキュメント
ReadMe
# TodoList Codex
React + TypeScript で構築したローカルストレージ対応の Todo 管理アプリです。
仕様の詳細は `docs/spec.md` を参照してください。
## 技術スタック
- React 19 / TypeScript 5 + Vite 7
- Jest 29 + React Testing Library
- localStorage を利用した永続化
## セットアップ
1. 必要条件: Node.js 18 以上(推奨 20+)、npm 10 以上。
2. 依存関係をインストール:
```bash
npm install
```
3. 開発サーバーを起動:
```bash
npm run dev
```
ブラウザで `http://localhost:5173` を開くとアプリを確認できます。
## テスト
Jest + React Testing Library で UI と永続化の動作を検証しています。
```bash
npm test
```
## ビルド
本番ビルドを作成して `dist` に出力します。
```bash
npm run build
```
仕様書
# TodoList 仕様書
## 1. 目的
ローカル環境で軽快に動作し、Todo の追加・削除・完了状態の切り替えを最小ステップで行える Web アプリを提供する。ネットワークが無くても利用できるよう localStorage で永続化する。
## 2. 機能要件
- **Todo の追加**: フォームで Todo 内容を入力し、「追加」ボタンまたは Enter で登録。空文字/空白のみは拒否。
- **Todo の削除**: 各行に配置された「削除」ボタンで該当 Todo をリストから除去。
- **完了状態の切り替え**: チェックボックス操作で completed フラグをトグル。完了済みは打ち消し線と淡色で表示。
- **進捗表示**: 「残り n 件 / 全 m 件」を常に表示し、リアルタイムに更新。
- **永続化**: 追加・削除・完了切り替えのたびに localStorage に保存。起動時は保存済みの配列を読み込む。
- **空状態メッセージ**: Todo が 0 件のとき誘導文を表示。
## 3. 非機能要件
- **レスポンシブ**: 320px 幅程度でも崩れない縦レイアウト。PC ではカード型の余白を確保。
- **アクセシビリティ**: フォームは label と aria-label を備え、状態ラベルは `aria-live="polite"` で通知。カラーコントラストは WCAG に沿った配色。
- **テスト**: Jest + React Testing Library で主要ユースケース(追加・削除・トグル・永続化)を自動化。
- **メンテ性**: 状態管理は `useTodos` フックで切り出し、`TodoItem` コンポーネントが UI を担当する分離構造。
## 4. アーキテクチャ概要
- `App.tsx`: レイアウト/フォーム/集計表示/Todo リストを描画するコンテナ。
- `TodoItem.tsx`: 単一 Todo 行(チェックボックス、本文、タイムスタンプ、削除ボタン)。
- `useTodos.ts`: localStorage 読み込み・書き込みと、`addTodo`・`toggleTodo`・`deleteTodo` を提供するカスタムフック。
- `Todo` 型: `id`, `text`, `completed`, `createdAt` を保持。
- `index.css`: ガラスカード風スタイルとフォーム、リストのレスポンシブ調整。
## 5. データモデル
```ts
type Todo = {
id: string; // crypto.randomUUID または代替 ID
text: string; // ユーザー入力(トリム済み)
completed: boolean; // true: 完了, false: 未完
createdAt: string; // ISO 8601 (new Date().toISOString())
}
```
- 保存キー: `todolist_codex.todos`
- 保存形式: `JSON.stringify(Todo[])`
## 6. 状態遷移
1. **初期化**: マウント時に localStorage を読み取り state 初期化。読み取れない/破損データは空配列扱い。
2. **追加**: `addTodo` が state に push → `useEffect` で永続化 → UI リスト再描画 → 入力欄はクリア。
3. **切替**: `toggleTodo` が該当 `completed` を反転 → state 更新 → 永続化 → 見た目更新。
4. **削除**: `deleteTodo` が指定 ID を除外 → state 更新 → 永続化。
## 7. UI 詳細
- 入力欄: ピル型テキストボックスと主要ボタン。無効時はボタンを `disabled`。
- リスト: 行ごとに背景カード、タイトル + サブ情報(作成日時)、右端に削除ボタン。
- 空状態: ラベンダー系背景で最初の Todo 追加を促す。
- 配色: 背景グラデーション + 白カード、アクセントカラーは `#6366f1`(ボタン)・`#ef4444`(削除)。
## 8. テストカバレッジ
1. 空状態の表示確認。
2. 追加操作が行リストと入力リセットに反映される。
3. チェックボックス操作で完了状態が切り替わる。
4. 削除ボタンで行が取り除かれる。
5. localStorage に保存された値をもとに再描画される(永続性)。
## 9. 起動手順概略
1. `npm install` で依存関係を導入。
2. `npm run dev` を実行し、Vite の開発サーバーを起動。
3. ブラウザで `http://localhost:5173` を開くとアプリを操作できる。
4. 品質確認は `npm test` で自動テストを実行。
詳細なセットアップやビルド方法はプロジェクトルートの `README.md` を参照。
まとめ
メリット
- モジュールまで全て導入してくれている。
- コードと文章の両方でたたき台を一瞬で作ってくれる。
- デザインまで丁寧に作ってもらえる。
デメリット
- 生成物をそのまま本番投入できる品質とは限らなそう。
- 抽象的なプロンプトだと、余計な実装が混じりそう。
- プロジェクト固有の文脈までは理解してくれない。
- トークンが途中で切れる問題がある。
今回 Todo リストの SPA を題材に、実装コード、テストコード、ドキュメントまで一通り Codex に書かせてみました。想定よりも丁寧に作成してくれたものの、やはり「すべてを任せる道具」というよりも「下地作りをかなり楽にしてくれる道具」と感じました。
特に個人的に最も気になっていたドキュメントの更新についてですが、プロジェクト全体を AI が読み取ってそのまま編集するため、不要な情報を混在させる可能性があります。その結果、かえって見づらくなってしまうケースも考えられます。また、トークン上限があるため、何度も指示をしてしまうと、実装に必要なトークンが不足してしまいます。自分が目指している仕様書を全自動化する未来は、まだまだ遠いと感じました。
しかしながら、全体的に初動のコストを大幅に下げることができるため、今後も AI と人手の境界を探りながら、 AI と友好な関係を築いていきたいですね。

徳田拓也 Takuya Tokuda
プロダクト開発統括部 dodaグロース開発部 dodaサイト開発1グループ
前職では不動産広告会社で賃貸物件を探すアプリケーション制作に参画。2021年10月にパーソルキャリアへ中途入社。現在は会員登録やカウンセリング予約領域の開発チームでエンジニアとして従事。
※2025年12月現在の情報です。
