0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ハーネスエンジニアリングでリアルタイム投票ツールを作った【WebSocket / Claude Code】

0
Last updated at Posted at 2026-06-27

作ったもの

チームミーティングで「賛成/反対/保留」をリアルタイム集計できる匿名投票ツール。

Claude Codeと一緒に、設計から実装まで約40分で作った。

vote-live というプロジェクト名で作った。

  • URLを共有するだけで参加できる
  • 投票結果がリアルタイムで全員の画面に反映される
  • ?host=true をつけるとリセットボタンが出るホストモードになる
  • 投票後に取り消しもできる

スクリーンショット 2026-06-27 15.46.53.png


ハーネスエンジニアリングとは

今回、コードを書く前にハーネスエンジニアリングという考え方を取り入れた。

AIエージェントが動作する「環境そのもの」を設計する行為。モデルを改善するのではなく、モデルの外側(ツール・ルール・権限・フィードバック)を整備して、AIが安定して成果を出せる実行環境を作ること。

4つの設計領域がある:

設計 内容
コンテキスト設計 AIに渡す情報を必要なものだけに絞る
行動設計 何をしていいか・ダメかを仕組みとして定義する
フィードバック設計 完了条件・テスト・検証を自動化する
運用設計 進捗を外部ファイルに記録し、小さく分割して進める

要するに「Claude Codeに頼む前に環境を整える」という話だ。

※ 本記事では以下の記事を参考にこの言葉を使っています。


先に設計書とAGENTS.mdを書いた

コードより先にこの2ファイルを書いた。

DESIGN.md(設計書)

機能要件・技術スタック・メッセージ仕様・ディレクトリ構成を書いた。

技術選定の決定事項も記録:

項目 決定 理由
ホスティング Railway WebSocket対応・無料枠あり
永続化 メモリのみ シンプルに始める
ホスト判定 URLパラメータ ?host=true でシンプルに
WebSocketライブラリ ws(素のライブラリ) Socket.ioは使わない

AGENTS.md(ハーネス設計)

Claude Codeへの指示書として書いた。

## 行動設計(何をしていいか・ダメか)

### ✅ 許可
- server/ 以下のファイルの作成・編集
- client/ 以下のファイルの作成・編集
- npm install の実行

### ❌ 禁止
- `DESIGN.md` の無断変更(設計変更は人間が判断する)
- DBの追加(メモリのみと決定済み)
- Socket.io の使用(素の ws ライブラリを使う)
- フレームワーク(React/Vue等)の導入

完了チェックリストも定義した:

## フィードバック設計(完了の定義)

### サーバー
- [ ] node server/index.js でエラーなく起動する
- [ ] 投票メッセージを受け取り、全クライアントにbroadcastできる

### 結合テスト
- [ ] ブラウザを2タブ開いて投票すると両方の画面に反映される

WebSocketのコード

技術スタックは Node.js + ws ライブラリ のみ。フレームワークなし。

サーバー(server/index.js)

const http = require('http');
const WebSocket = require('ws');

// HTTPサーバーとWebSocketサーバーを同じポートで動かす
const server = http.createServer(/* 静的ファイル配信 */);
const wss = new WebSocket.Server({ server });

let votes = { agree: 0, disagree: 0, neutral: 0 };

function broadcast(data) {
  const message = JSON.stringify(data);
  wss.clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

wss.on('connection', (ws) => {
  // 接続直後に現在の投票状況を返す
  ws.send(JSON.stringify({ type: 'update', result: votes }));

  ws.on('message', (data) => {
    const message = JSON.parse(data);

    if (message.type === 'vote') {
      votes[message.choice]++;
      broadcast({ type: 'update', result: votes });
    } else if (message.type === 'cancel') {
      if (votes[message.choice] > 0) votes[message.choice]--;
      broadcast({ type: 'update', result: votes });
    } else if (message.type === 'reset') {
      votes = { agree: 0, disagree: 0, neutral: 0 };
      broadcast({ type: 'update', result: votes });
    }
  });
});

ポイントは2つ。

① HTTPサーバーとWebSocketを同じポートに相乗りさせる
Railwayは1プロセス1ポートなので、HTTPとWebSocketを分けられない。new WebSocket.Server({ server }) で同じポートに相乗りさせることで解決した。

② broadcastで全クライアントに配信
誰かが投票したら wss.clients をループして全員に送る。これがWebSocketのリアルタイム性の核心。

クライアント(app.js)

// URLからWebSocketのURLを自動生成
// ローカル → ws://localhost:8080
// 本番    → wss://ドメイン名
const wsProtocol = location.protocol === 'https:' ? 'wss' : 'ws';
const ws = new WebSocket(`${wsProtocol}://${location.host}`);

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);
  if (message.type === 'update') {
    updateResult(message.result); // バーグラフを更新
  }
};

// 投票ボタン
btn.addEventListener('click', () => {
  ws.send(JSON.stringify({ type: 'vote', choice: 'agree' }));
});

ローカルと本番でURLが変わる問題は location.host を使うことで吸収した。


メッセージ設計

クライアントとサーバー間のやりとりはシンプルなJSONにした。

// 投票
{ "type": "vote", "choice": "agree" | "disagree" | "neutral" }

// 取り消し
{ "type": "cancel", "choice": "agree" }

// リセット(ホストのみ)
{ "type": "reset" }

// サーバー  全員:結果更新
{ "type": "update", "result": { "agree": 3, "disagree": 1, "neutral": 2 } }

やってみた感想

設計書とAGENTS.mdを先に書いて正解だった

AGENTS.mdで「Socket.ioは使わない」「DBは使わない」と書いておいたことで、Claude Codeが勝手に余計なものを入れることがなかった。制約を事前に言語化しておくことの効果をはっきり感じた。

WebSocketはシンプルだった

ws.send / ws.onmessage / broadcast の3つを理解すれば、リアルタイム通信の本質はほぼ掴める。フレームワークを使わず素の ws ライブラリで作ったことで、中身が見えてよかった。

完了チェックリストが効いた

AGENTS.mdに「2タブで同期確認」という結合テストを書いておいたおかげで、単体テストだけで終わらなかった。完了条件を事前に定義することの重要さを改めて感じた。


まとめ

  • コードより先に設計書・AGENTS.mdを書いた
  • 制約を言語化しておくことでClaude Codeが迷わない
  • WebSocketはHTTPサーバーと同じポートに相乗りさせると便利
  • ws ライブラリ1本でリアルタイム通信は十分作れる
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?