コメント・アクティビティ 仕様書
ステータス: Draft / 作成日: 2026-05-27 PR #4 — 依存: コア
1. データモデル
1.1 task_comments
pub struct Model {
pub id: Uuid,
pub task_id: Uuid,
pub user_id: Uuid,
pub body: String, // Markdown(@メンション含む)
pub parent_comment_id: Option<Uuid>, // スレッド返信(1 段のみ)
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub deleted_at: Option<DateTimeUtc>, // ソフトデリート
}
1.2 task_activities
タスクへのあらゆる変更を自動記録する監査ログ。アプリ層から明示的に書き込む。
event_type 一覧:
GitHub Issue の一括取り込みと webhook による新規作成は github_issue_imported、
リンク済みタスクへの変更反映は github_issue_synced を記録する。
どちらも user_id = NULL(システム操作)で、タスク・同期リンクと同じトランザクションで保存する。
再取り込み・同じ内容の通知・古い通知・書き戻しの跳ね返りでは履歴を増やさない。
画面には同期元のリポジトリ名と Issue 番号を表示する。導入前の履歴は遡って作成しない。
2. マイグレーション
CREATE TABLE task_comments (
id UUID PRIMARY KEY,
task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
body TEXT NOT NULL,
parent_comment_id UUID REFERENCES task_comments(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ
);
CREATE TABLE task_activities (
id UUID PRIMARY KEY,
task_id UUID NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
event_type VARCHAR NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_comments_task ON task_comments(task_id, created_at)
WHERE deleted_at IS NULL;
CREATE INDEX idx_activities_task ON task_activities(task_id, created_at DESC);
3. @メンション処理
コメント本文中の @ユーザー名 をサーバー側でパースし、通知を生成する。
パース対象: @username 形式(アルファベット・数字・_・- を含む)
1. body から正規表現で @mention を抽出
2. username でユーザーを検索
3. 見つかった場合 → notifications テーブルに type=mentioned で挿入
(通知システムは PR #5 で実装。本 PR では mention 抽出のみ行い、
通知テーブルが存在しない場合は INSERT をスキップする)
4. API
コメント
POST リクエスト:
{
"body": "この件は @tanaka さんに確認してください。",
"parent_comment_id": null
}
GET レスポンス(スレッド構造):
{
"comments": [
{
"id": "uuid",
"user": { "id": "uuid", "name": "田中" },
"body": "設計は完了しました。",
"replies": [
{
"id": "uuid",
"user": { "id": "uuid", "name": "鈴木" },
"body": "レビュー依頼します。",
"created_at": "2026-05-27T12:00:00Z"
}
],
"created_at": "2026-05-27T11:00:00Z",
"updated_at": "2026-05-27T11:00:00Z",
"is_deleted": false
}
]
}
削除済みコメントは "body": null, "is_deleted": true で返す(返信があれば親は残す)。
アクティビティ
レスポンス:
{
"activities": [
{
"id": "uuid",
"event_type": "status_changed",
"user": { "id": "uuid", "name": "田中" },
"payload": { "from": "Backlog", "to": "In Review" },
"created_at": "2026-05-27T11:00:00Z"
}
]
}
5. フロントエンド(Phase B)
タスク詳細のコメントセクション(実装済み)
タスク詳細ページに、コメントだけの独立セクションとして実装している。
──── コメント ────────────────────────────────────
🧑 田中 11:10
設計は完了しました。
└ 🧑 鈴木 11:15
レビュー依頼します。
[返信フォーム]
[新規投稿フォーム]
-
一覧はクライアント取得(vue-query)。取得失敗はセクション内だけで倒し、 再試行導線を出す。投稿フォームは取得失敗時も残す(GET と POST は独立)
-
投稿・返信(1 段スレッド)・編集・削除。削除済みコメントはプレースホルダ表示で、 編集・削除・返信の導線を出さない
-
編集ボタンは投稿者本人のコメントにのみ表示(backend は本人以外を 403 にする)。 削除ボタンは全コメントに表示し、可否は backend 判定に委ねる (テナントオーナーにも許可されており frontend からは判定できないため)
-
削除済みでないコメントのうち
updated_at != created_atのものに「(編集済み)」を 表示する(backend は作成時に両カラムへ同一時刻を入れる。削除済みは プレースホルダ表示のため対象外) -
本文は素テキスト表示(改行保持・エスケープ)
コンポーネント
残件
-
アクティビティとの時系列混在表示(TaskTimeline): 当初案のコメントと アクティビティを混在させるタイムラインは未実装。
GET /tasks/{id}/activitiesの消費側も未接続 -
コメント本文の Markdown(KFM)描画: 本文はデータとしては Markdown (@メンション含む)を想定するが、表示は素テキストのまま。KFM レンダラの コメント本文への接続は
docs/frontend/kfm-phase1-implementation.mdでも 未接続の残件とされている