# 画面設計書 138-スニペット詳細

## 概要

本ドキュメントは、GitLabにおけるプロジェクトスニペット詳細画面の設計を定義するものである。

### 本画面の処理概要

**業務上の目的・背景**：スニペットの詳細内容を確認し、コードの共有やレビュー、ディスカッションを行うための画面である。スニペットの完全なコンテンツを表示し、構文ハイライト付きでコードを閲覧できる。また、アワード絵文字によるリアクションやノート（コメント）機能により、チームメンバー間でのコミュニケーションが可能となる。

**画面へのアクセス方法**：スニペット一覧画面からスニペットのタイトルをクリック、または直接URLでアクセスできる。URLパスは `/:namespace/:project/-/snippets/:id` となる。

**主要な操作・処理内容**：
1. スニペットのタイトル、説明、コンテンツを表示
2. 構文ハイライト付きでコードを表示
3. アワード絵文字（リアクション）の追加・削除
4. ノート（コメント）の投稿・編集・削除
5. スニペットの編集画面への遷移（権限あり時）
6. スニペットの削除（権限あり時）
7. スパムとしてマーク（管理者権限時）
8. 不正利用報告

**画面遷移**：
- 遷移元：スニペット一覧画面、直接リンク
- 遷移先：スニペット編集画面、不正利用報告画面

**権限による表示制御**：スニペットの可視性と閲覧者の権限に応じて表示内容が変わる。プロジェクトメンバーまたは管理者は非公開スニペットも閲覧可能。`update_snippet` 権限を持つユーザーには編集ボタンが表示される。スパム対象としてマーク可能なユーザーにはマークボタンが表示される。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 71 | プロジェクトスニペット | 主機能 | スニペットの詳細表示 |

## 画面種別

詳細

## URL/ルーティング

| メソッド | パス | コントローラ#アクション |
|---------|------|------------------------|
| GET | `/:namespace_id/:project_id/-/snippets/:id` | `projects/snippets#show` |
| POST | `/:namespace_id/:project_id/-/snippets/:id/toggle_award_emoji` | `projects/snippets#toggle_award_emoji` |
| POST | `/:namespace_id/:project_id/-/snippets/:id/mark_as_spam` | `projects/snippets#mark_as_spam` |
| GET | `/:namespace_id/:project_id/-/snippets/:id/raw` | `projects/snippets#raw` |

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入出力 | バリデーション | 説明 |
|--------|--------|-----|------|--------|---------------|------|
| プロジェクトID | project_id | String | Yes | 入力 | プロジェクト存在チェック | 対象プロジェクト |
| スニペットID | id | Integer | Yes | 入力 | スニペット存在チェック | 対象スニペット |

## 表示項目

この画面はVue.jsコンポーネント（`#js-snippet-view`）によりレンダリングされる。

| 項目名 | 項目ID | 型 | 説明 |
|--------|--------|-----|------|
| パンくずリスト | breadcrumbs | Component | Snippets > $参照番号 |
| ページタイトル | page_title | String | スニペットタイトル（参照番号） |
| スニペットビュー | snippet_view | Vue Component | スニペット詳細表示 |
| アワード絵文字 | award_emoji | Component | リアクション絵文字ブロック |
| ノートセクション | notes | Component | コメント一覧とフォーム |

### Vueコンポーネントへ渡すデータ

| 項目名 | 説明 |
|--------|------|
| snippet-gid | スニペットのGlobal ID |
| report-abuse-path | 不正利用報告画面へのパス |
| can-report-spam | スパムとしてマーク可能か |

## イベント仕様

### 1-アワード絵文字追加/削除

**トリガー**：絵文字ボタンクリック

**処理フロー**：
1. toggle_award_emoji APIを呼び出し
2. 絵文字の追加または削除を実行
3. 絵文字カウントを更新

### 2-ノート投稿

**トリガー**：コメントフォームで「Comment」ボタンクリック

**処理フロー**：
1. Notes APIを呼び出し
2. 新しいノートを作成
3. ノート一覧を更新

### 3-スニペット編集

**トリガー**：「Edit」ボタンクリック（Vueコンポーネント内）

**処理フロー**：
1. スニペット編集画面へ遷移
2. URLは `/:namespace/:project/-/snippets/:id/edit`

### 4-不正利用報告

**トリガー**：「Report abuse」リンククリック

**処理フロー**：
1. snippet_report_abuse_path へ遷移
2. 不正利用報告フォームを表示

### 5-スパムとしてマーク

**トリガー**：「Mark as spam」ボタンクリック（管理者権限時）

**処理フロー**：
1. mark_as_spam APIを呼び出し
2. Akismetにスパム報告
3. 成功/失敗メッセージを表示

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 詳細表示 | snippets | SELECT | スニペット取得 |
| 絵文字追加 | award_emoji | INSERT | 絵文字追加 |
| 絵文字削除 | award_emoji | DELETE | 絵文字削除 |
| ノート投稿 | notes | INSERT | コメント追加 |
| スパムマーク | snippets | UPDATE | スパムフラグ更新 |

### テーブル別更新項目詳細

#### award_emoji（絵文字追加時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | awardable_type | 'Snippet' | 対象モデルタイプ |
| INSERT | awardable_id | スニペットID | 対象ID |
| INSERT | name | 絵文字名 | 例：thumbsup |
| INSERT | user_id | current_user.id | 付与者 |

#### notes（コメント投稿時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | noteable_type | 'Snippet' | 対象モデルタイプ |
| INSERT | noteable_id | スニペットID | 対象ID |
| INSERT | author_id | current_user.id | 投稿者 |
| INSERT | note | コメント内容 | Markdown対応 |
| INSERT | project_id | プロジェクトID | FK |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| MSG001 | 成功 | Snippet reported as spam. | スパムマーク成功時 |
| MSG002 | エラー | Failed to mark as spam. | スパムマーク失敗時 |

## 例外処理

| 例外条件 | 処理内容 | 遷移先 |
|---------|---------|--------|
| スニペット未検出 | 404 Not Found | Not Found画面 |
| 権限不足 | 404 Not Found | Not Found画面 |
| スニペット機能無効 | 404 Not Found | Not Found画面 |

## 備考

- この画面はVue.jsコンポーネント（`#js-snippet-view`）によりレンダリングされる
- スニペットはGlobal ID（`snippet.to_global_id`）で識別される
- アワード絵文字はAPIパス `project_snippets_award_api_path` で操作
- カスタム絵文字はグループ設定に依存（`new_custom_emoji_path(@project.group)`）
- ノートセクションは `shared/notes/notes_with_form` パーシャルで表示
- `submittable_as_spam_by?` でスパムマーク権限をチェック

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

Snippetモデルと関連機能を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | snippet.rb | `app/models/snippet.rb` | Snippetモデル、Noteable、Awardableの組み込み |
| 1-2 | noteable.rb | `app/models/concerns/noteable.rb` | コメント機能の組み込み |
| 1-3 | awardable.rb | `app/models/concerns/awardable.rb` | 絵文字機能の組み込み |

**読解のコツ**: `include Noteable` でノート機能、`include Awardable` で絵文字機能が追加される。これらのConcernの理解が重要。

#### Step 2: エントリーポイントを理解する

ビューテンプレートとコントローラの処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | show.html.haml | `app/views/projects/snippets/show.html.haml` | スニペット詳細画面のビュー |
| 2-2 | snippets_controller.rb | `app/controllers/projects/snippets_controller.rb` | showアクション、関連アクション |

**主要処理フロー**:
1. **行11**: `snippet` メソッドでスニペット取得（before_action）
2. **行14**: `authorize_read_snippet!` で閲覧権限チェック
3. Vueコンポーネントに必要なデータをdata属性で渡す
4. アワード絵文字ブロックをレンダリング
5. ノートセクションをレンダリング

#### Step 3: Vueコンポーネントを理解する

フロントエンドの実装を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | snippet_view | `app/assets/javascripts/snippets/` | Vueコンポーネント群 |

#### Step 4: 共有コンポーネントを理解する

アワード絵文字とノート機能を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | _awards_block.html.haml | `app/views/award_emoji/_awards_block.html.haml` | 絵文字ブロック表示 |
| 4-2 | _notes_with_form.html.haml | `app/views/shared/notes/_notes_with_form.html.haml` | ノートセクション |

### プログラム呼び出し階層図

```
projects/snippets/show.html.haml
    │
    ├─ パンくずリスト
    │      └─ Snippets > $参照番号
    │
    ├─ #js-snippet-view (Vue Component)
    │      │
    │      ├─ data: snippet-gid
    │      ├─ data: report-abuse-path
    │      └─ data: can-report-spam
    │             │
    │             └─ GraphQL Query -> Snippet詳細取得
    │                    │
    │                    ├─ タイトル、説明表示
    │                    ├─ コンテンツ（構文ハイライト）
    │                    └─ 編集/削除ボタン
    │
    ├─ award_emoji/_awards_block.html.haml
    │      │
    │      ├─ 絵文字一覧表示
    │      └─ 絵文字追加ボタン
    │             └─ toggle_award_emoji API
    │
    └─ shared/notes/_notes_with_form.html.haml
           │
           ├─ ノート一覧
           └─ ノート投稿フォーム
```

### データフロー図

```
[入力]                    [処理]                         [出力]

snippet_id ──────▶ SnippetsController#show ──────▶ @snippet 取得
                           │
                           ▼
                    authorize_read_snippet!
                           │
                           ▼
               #js-snippet-view マウント
                           │
                           ▼
               GraphQL Query (snippet-gid)
                           │
                           ▼
               Snippet詳細データ取得
                           │
                           ▼
               Vue Component レンダリング


[絵文字追加時]

絵文字選択 ──────▶ toggle_award_emoji API ──────▶ award_emoji INSERT
                                                         │
                                                         ▼
                                              絵文字カウント更新


[ノート投稿時]

コメント入力 ──────▶ Notes API ──────▶ notes INSERT
                                              │
                                              ▼
                                      ノート一覧更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| show.html.haml | `app/views/projects/snippets/show.html.haml` | テンプレート | スニペット詳細画面 |
| snippets_controller.rb | `app/controllers/projects/snippets_controller.rb` | コントローラ | HTTPリクエスト処理 |
| snippet.rb | `app/models/snippet.rb` | モデル | Snippetエンティティ |
| _awards_block.html.haml | `app/views/award_emoji/_awards_block.html.haml` | パーシャル | 絵文字ブロック |
| _notes_with_form.html.haml | `app/views/shared/notes/_notes_with_form.html.haml` | パーシャル | ノートセクション |
| project.rb | `config/routes/project.rb` | ルーティング | URLルーティング定義（行433-437） |
| snippets_actions.rb | `app/controllers/concerns/snippets_actions.rb` | Concern | 共通アクション |
