# 機能設計書 85-ステガノグラフィエンコーディング

## 概要

本ドキュメントは、Decap CMSのステガノグラフィエンコーディング機能について記述する。Vercel Stega（`@vercel/stega`）を使用して、エントリデータにビジュアル編集用のメタデータを不可視文字として埋め込む機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：ビジュアル編集（Visual Editing）機能において、フロントエンドに表示されるコンテンツとCMSのフィールドを対応付ける必要がある。ステガノグラフィエンコーディングは、テキストコンテンツに不可視のUnicode文字としてフィールドパス情報を埋め込むことで、フロントエンド側がどのCMSフィールドに対応するかを識別できるようにする。これにより、プレビュー画面上のテキストをクリックして直接該当フィールドの編集に遷移する等のインタラクティブな編集体験が可能になる。

**機能の利用シーン**：エントリデータのプレビュー表示時に、string/text/markdownウィジェットの値にステガノグラフィデータが埋め込まれ、ビジュアル編集対応のフロントエンドがこのデータを読み取ってCMSフィールドとの対応を識別する。

**主要な処理内容**：
1. `encodeEntry` - エントリデータ全体を再帰的に走査してステガノグラフィデータを埋め込む
2. `encodeString` - 文字列値にフィールドパス情報を付加（string/text/markdownウィジェット対応）
3. `encodeList` - リスト値の各要素を再帰的にエンコード（型付きリスト対応）
4. `encodeMap` - マップ値の各キーに対応するフィールドを検索して再帰的にエンコード
5. `getNestedFields` - フィールド定義からネストされたフィールドを取得（types/fields/field対応）
6. パスベースのキャッシュによるエンコード結果の再利用

**関連システム・外部連携**：`@vercel/stega`（Vercel Steganographyライブラリ）、Immutable.js

**権限による制御**：フィールドレベルで`visualEditing: false`を設定することでエンコードを無効化可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | プレビューペインでのステガノグラフィデータ埋め込み |

## 機能種別

データ変換 / エンコーディング

## 入力仕様

### 入力パラメータ

#### encodeEntry

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| value | unknown | Yes | エントリデータ（Immutable Map/List/string等） | - |
| fields | List<ImmutableMap<string, unknown>> | Yes | CMSフィールド定義のリスト | - |

#### encodeString

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| value | string | Yes | エンコード対象の文字列 | - |
| context.fields | CmsField[] | Yes | 現在のフィールド定義 | - |
| context.path | string | Yes | オブジェクトツリー内のパス | - |

### 入力データソース

エントリのフィールドデータ（Immutable Map）、コレクションのフィールド定義

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| encodedValue | unknown | ステガノグラフィデータが埋め込まれたエントリデータ |

### 出力先

プレビューペイン表示、ビジュアル編集対応のフロントエンド

## 処理フロー

### 処理シーケンス

```
1. encodeEntry呼び出し
   ├─ fields（Immutable）をプレーンJSに変換
   └─ visit関数で再帰的走査開始

2. visit関数（再帰）
   ├─ キャッシュチェック（パスベース）
   ├─ Immutable List → encodeList
   ├─ Immutable Map → encodeMap
   ├─ string → encodeString
   └─ その他 → そのまま返却

3. encodeString
   ├─ フィールドのwidget種別チェック
   ├─ string/text → 値末尾にvercelStegaEncode({decap: path})を付加
   ├─ markdown → 各段落の末尾にステガデータを付加
   └─ visualEditing: false → エンコードスキップ

4. encodeList
   ├─ 各要素を走査
   ├─ ImmutableMap要素 → typeフィールドで型付きリスト判定
   └─ 各要素をvisitで再帰的にエンコード

5. encodeMap
   ├─ 各キーに対応するフィールド定義を検索
   ├─ getNestedFieldsでネストフィールドを取得
   └─ visitで再帰的にエンコード
```

### フローチャート

```mermaid
flowchart TD
    A[encodeEntry] --> B[fields.toJS変換]
    B --> C[visit関数開始]
    C --> D{キャッシュヒット?}
    D -->|Yes| E[キャッシュ値を返却]
    D -->|No| F{値の型判定}
    F -->|List| G[encodeList]
    F -->|Map| H[encodeMap]
    F -->|string| I[encodeString]
    F -->|other| J[そのまま返却]
    G --> K[各要素をvisitで再帰]
    H --> L[各キーをvisitで再帰]
    I --> M{widget種別}
    M -->|string/text| N[末尾にstega付加]
    M -->|markdown| O[各段落にstega付加]
    M -->|other| P[そのまま返却]
    N --> Q[キャッシュ保存]
    O --> Q
    P --> Q
    K --> Q
    L --> Q
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-85-01 | 対象ウィジェット | string、text、markdownウィジェットのみがステガノグラフィエンコードの対象 | encodeString実行時 |
| BR-85-02 | visualEditing無効化 | フィールドに`visualEditing: false`が設定されている場合、そのフィールドはエンコードされない | string/textウィジェットでvisualEditing設定がある場合 |
| BR-85-03 | Markdown段落分割 | markdownフィールドは`\n\n`で段落分割し、各段落にステガデータを付加する | markdownウィジェットの場合 |
| BR-85-04 | 型付きリスト対応 | リスト内のオブジェクトにtypeフィールドがある場合、その型に対応するフィールド定義を使用 | encodeList実行時 |
| BR-85-05 | キャッシュ戦略 | パスベースのキャッシュにより、同一パスの値が変更されていなければ再エンコードをスキップ | visit関数実行時 |

### 計算ロジック

- ステガデータ生成: `vercelStegaEncode({ decap: path })` でパス情報をUnicode不可視文字にエンコード
- Markdown段落分割: `value.split(/(\n\n+)/)` で段落を分割し、空でないブロックにのみステガデータを付加

## データベース操作仕様

該当なし

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

なし

## エラー処理

### エラーケース一覧

本機能は明示的なエラーハンドリングを行わない。フィールド定義が見つからない場合は値をそのまま返す。

### リトライ仕様

該当なし

## トランザクション仕様

該当なし

## パフォーマンス要件

- パスベースのキャッシュ（`encodingCache`）により、キーストローク毎のre-encodeを防止
- 大量のフィールドを持つエントリでも再帰走査のため計算量はO(n)（nはフィールド総数）

## セキュリティ考慮事項

- ステガノグラフィデータにはCMSフィールドのパス情報が含まれるため、公開コンテンツにパス構造が露出する
- ビジュアル編集を使用しない本番環境ではエンコードを無効化すべき

## 備考

- `@vercel/stega`はVercelが開発したステガノグラフィライブラリで、不可視Unicode文字でデータを埋め込む
- キャッシュはグローバルな`Map`オブジェクトとして保持されるため、メモリリークの可能性がある
- `isImmutableMap`/`isImmutableList`はtypes.tsで定義された型ガード関数

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | stega.ts | `packages/decap-cms-core/src/lib/stega.ts` | EncodeContextインターフェース（fields、path、visit） |
| 1-2 | types.ts | `packages/decap-cms-lib-util/src/types.ts` | isImmutableMap/isImmutableList型ガード関数 |

**読解のコツ**: ビジターパターン（visit関数）を使った再帰的な走査が中核。EncodeContextにvisit関数自体が含まれており、再帰呼び出しを可能にしている。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | stega.ts | `packages/decap-cms-core/src/lib/stega.ts` | encodeEntry関数 |

**主要処理フロー**:
1. **121-145行目**: encodeEntry - エントリデータのエンコード開始（fields.toJS()でプレーンJS化）
2. **124-142行目**: visit関数（クロージャ） - キャッシュチェック→型判定→エンコード→キャッシュ保存
3. **115行目**: encodingCache - パスベースのグローバルキャッシュ

#### Step 3: 各エンコード処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | stega.ts | `packages/decap-cms-core/src/lib/stega.ts` | encodeString/encodeList/encodeMap/getNestedFields |

**主要処理フロー**:
- **20-34行目**: getNestedFields - types/fields/fieldからネストフィールドを抽出
- **40-55行目**: encodeString - widget種別に応じたステガデータ埋め込み
- **61-87行目**: encodeList - リスト要素の再帰エンコード（型付きリスト対応）
- **93-109行目**: encodeMap - マップキーの再帰エンコード

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

```
encodeEntry（stega.ts 121行目）
    |
    +-- fields.toJS()（Immutable→プレーンJS変換）
    +-- visit（再帰ビジター）
           |
           +-- encodingCache.get（キャッシュチェック）
           +-- [List] encodeList
           |      +-- isImmutableMap判定
           |      +-- [typed] fields.find(f => f.name === itemType)
           |      +-- visit（再帰）
           |
           +-- [Map] encodeMap
           |      +-- fields.find(f => f.name === key)
           |      +-- getNestedFields
           |      +-- visit（再帰）
           |
           +-- [string] encodeString
           |      +-- vercelStegaEncode({ decap: path })
           |      +-- [markdown] value.split(/(\n\n+)/)
           |
           +-- encodingCache.set（キャッシュ保存）
```

### データフロー図

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

エントリデータ ──────────▶ encodeEntry ────────────────▶ ステガ埋込済みデータ
(Immutable Map)               |
                        visit（再帰ビジター）
                              |
フィールド定義 ─────────▶     +-- encodeString
(CmsField[])                  |     +-- vercelStegaEncode
                              +-- encodeList
                              +-- encodeMap
                              |
                        encodingCache
                        (パスベースキャッシュ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| stega.ts | `packages/decap-cms-core/src/lib/stega.ts` | ソース | ステガノグラフィエンコーディングの全実装 |
| types.ts | `packages/decap-cms-lib-util/src/types.ts` | ソース | isImmutableMap/isImmutableList型ガード |
| @vercel/stega | 外部ライブラリ | 依存 | Vercelステガノグラフィエンコーディングライブラリ |
