Yuki Takei 4 месяцев назад
Родитель
Сommit
dced395f8a

+ 0 - 186
.serena/memories/apps-app-pagetree-performance-refactor-plan.md

@@ -1,186 +0,0 @@
-# PageTree パフォーマンス改善リファクタ計画 - 現実的戦略
-
-## 🎯 目標
-現在のパフォーマンス問題を解決:
-- **問題**: 5000件の兄弟ページで初期レンダリングが重い
-- **目標**: 表示速度を10-20倍改善、UX維持
-
-## ✅ 戦略2: API軽量化 - **完了済み**
-
-### 実装済み内容
-- **ファイル**: `apps/app/src/server/service/page-listing/page-listing.ts:77`
-- **変更内容**: `.select('_id path parent descendantCount grant isEmpty createdAt updatedAt wip')` を追加
-- **型定義**: `apps/app/src/interfaces/page.ts` の `IPageForTreeItem` 型も対応済み
-- **追加改善**: 計画にはなかった `wip` フィールドも最適化対象に含める
-
-### 実現できた効果
-- **データサイズ**: 推定 500バイト → 約100バイト(5倍軽量化)
-- **ネットワーク転送**: 5000ページ時 2.5MB → 500KB程度に削減
-- **状況**: **実装完了・効果発現中**
-
----
-
-## 🚀 戦略1: 既存アーキテクチャ活用 + headless-tree部分導入 - **現実的戦略**
-
-### 前回のreact-window失敗原因
-1. **動的itemCount**: ツリー展開時にアイテム数が変化→react-windowの前提と衝突
-2. **非同期ローディング**: APIレスポンス待ちでフラット化不可
-3. **複雑な状態管理**: SWRとreact-windowの状態同期が困難
-
-### 現実的制約の認識
-**ItemsTree/TreeItemLayoutは廃止困難**:
-- **CustomTreeItemの出し分け**: `PageTreeItem` vs `TreeItemForModal`  
-- **共通副作用処理**: rename/duplicate/delete時のmutation処理
-- **多箇所からの利用**: PageTree, PageSelectModal, AiAssistant等
-
-## 📋 修正された実装戦略: **ハイブリッドアプローチ**
-
-### **核心アプローチ**: ItemsTreeを**dataProvider**として活用
-
-**既存の責務は保持しつつ、内部実装のみheadless-tree化**:
-
-1. **ItemsTree**: UIロジック・副作用処理はそのまま
-2. **TreeItemLayout**: 個別アイテムレンダリングはそのまま  
-3. **データ管理**: 内部でheadless-treeを使用(SWR → headless-tree)
-4. **Virtualization**: ItemsTree内部にreact-virtualを導入
-
-### **実装計画: 段階的移行**
-
-#### **Phase 1: データ層のheadless-tree化**
-
-**ファイル**: `ItemsTree.tsx`
-```typescript
-// Before: 複雑なSWR + 子コンポーネント管理
-const tree = useTree<IPageForTreeItem>({
-  rootItemId: initialItemNode.page._id,
-  dataLoader: {
-    getItem: async (itemId) => {
-      const response = await apiv3Get('/page-listing/item', { id: itemId });
-      return response.data;
-    },
-    getChildren: async (itemId) => {
-      const response = await apiv3Get('/page-listing/children', { id: itemId });
-      return response.data.children.map(child => child._id);
-    },
-  },
-  features: [asyncDataLoaderFeature],
-});
-
-// 既存のCustomTreeItemに渡すためのアダプター
-const adaptedNodes = tree.getItems().map(item => 
-  new ItemNode(item.getItemData())
-);
-
-return (
-  <ul className={`${moduleClass} list-group`}>
-    {adaptedNodes.map(node => (
-      <CustomTreeItem
-        key={node.page._id}
-        itemNode={node}
-        // ... 既存のpropsをそのまま渡す
-        onRenamed={onRenamed}
-        onClickDuplicateMenuItem={onClickDuplicateMenuItem}
-        onClickDeleteMenuItem={onClickDeleteMenuItem}
-      />
-    ))}
-  </ul>
-);
-```
-
-#### **Phase 2: Virtualization導入**
-
-**ファイル**: `ItemsTree.tsx` (Phase1をベースに拡張)
-```typescript
-const virtualizer = useVirtualizer({
-  count: adaptedNodes.length,
-  getScrollElement: () => containerRef.current,
-  estimateSize: () => 40,
-});
-
-return (
-  <div ref={containerRef} className="tree-container">
-    <div style={{ height: virtualizer.getTotalSize() }}>
-      {virtualizer.getVirtualItems().map(virtualItem => {
-        const node = adaptedNodes[virtualItem.index];
-        return (
-          <div
-            key={node.page._id}
-            style={{
-              position: 'absolute',
-              top: virtualItem.start,
-              height: virtualItem.size,
-              width: '100%',
-            }}
-          >
-            <CustomTreeItem
-              itemNode={node}
-              // ... 既存props
-            />
-          </div>
-        );
-      })}
-    </div>
-  </div>
-);
-```
-
-#### **Phase 3 (将来): 完全なheadless-tree移行**
-
-最終的にはdrag&drop、selection等のUI機能もheadless-treeに移行可能ですが、**今回のスコープ外**。
-
-## 📁 現実的なファイル変更まとめ
-
-| アクション | ファイル | 内容 | スコープ |
-|---------|---------|------|------|
-| ✅ **完了** | **apps/app/src/server/service/page-listing/page-listing.ts** | selectクエリ追加 | API軽量化 |
-| ✅ **完了** | **apps/app/src/interfaces/page.ts** | IPageForTreeItem型定義 | API軽量化 |
-| 🔄 **修正** | **src/client/components/ItemsTree/ItemsTree.tsx** | headless-tree + virtualization導入 | **今回の核心** |
-| 🆕 **新規** | **src/client/components/ItemsTree/usePageTreeDataLoader.ts** | データローダー分離 | 保守性向上 |
-| ⚠️ **保持** | **src/client/components/TreeItem/TreeItemLayout.tsx** | 既存のまま(後方互換) | 既存責務保持 |
-| ⚠️ **部分削除** | **src/stores/page-listing.tsx** | useSWRxPageChildren削除 | 重複排除 |
-
-**新規ファイル**: 1個(データローダー分離のみ)  
-**変更ファイル**: 2個(ItemsTree改修 + store整理)  
-**削除ファイル**: 0個(既存アーキテクチャ尊重)
-
----
-
-## 🎯 実装優先順位
-
-**✅ Phase 1**: API軽量化(低リスク・即効性) - **完了**
-
-**📋 Phase 2-A**: ItemsTree内部のheadless-tree化
-- **工数**: 2-3日
-- **リスク**: 低(外部IF変更なし)
-- **効果**: 非同期ローディング最適化、キャッシュ改善
-
-**📋 Phase 2-B**: Virtualization導入  
-- **工数**: 2-3日
-- **リスク**: 低(内部実装のみ)
-- **効果**: レンダリング性能10-20倍改善
-
-**現在の効果**: API軽量化により 5倍のデータ転送量削減済み  
-**Phase 2完了時の予想効果**: 初期表示速度 20-50倍改善
-
----
-
-## 🏗️ 実装方針: **既存アーキテクチャ尊重**
-
-**基本方針**:
-- **既存のCustomTreeItem責務**は保持(rename/duplicate/delete等)
-- **データ管理層のみ**をheadless-tree化  
-- **外部インターフェース**は変更せず、内部最適化に集中
-- **段階的移行**で低リスク実装
-
-**今回のスコープ**:
-- ✅ 非同期データローディング最適化
-- ✅ Virtualizationによる大量要素対応  
-- ❌ drag&drop/selection(将来フェーズ)
-- ❌ 既存アーキテクチャの破壊的変更
-
----
-
-## 技術的参考資料
-- **headless-tree**: https://headless-tree.lukasbach.com/ (データ管理層のみ利用)
-- **react-virtual**: @tanstack/react-virtualを使用  
-- **アプローチ**: 既存ItemsTree内部でheadless-tree + virtualizationをハイブリッド活用

+ 303 - 0
.serena/memories/apps-app-simplified-items-tree-virtualization-plan.md

@@ -0,0 +1,303 @@
+# SimplifiedItemsTree作成とVirtualization対応 - 実装プラン
+
+## 🎯 目標
+
+PageTreeのvirtualizationを実現し、5000件の兄弟ページでも快適に動作させる
+
+**戦略**: 段階的な簡素化とAPI理解を優先し、デグレを防ぐ
+
+---
+
+## 📋 マイルストーン1: 最小限のSimplifiedItemsTree作成
+
+### 目的
+- **最小限の機能のみ**: ページリスト表示 + クリック遷移だけ
+- ツリー構造も不要(フラットリスト)
+- 既存APIも使わない(モックデータでOK)
+
+### 1.1. SimplifiedItemsTreeの作成
+
+**新規作成ファイル**:
+```
+src/client/components/SimplifiedItemsTree/
+├── SimplifiedItemsTree.tsx          # 50行程度
+├── SimplifiedTreeItem.tsx           # 30行程度
+├── SimplifiedItemsTree.module.scss  # 最小限
+└── index.ts
+```
+
+**SimplifiedItemsTreeの仕様**(超シンプル版):
+- **機能**:
+  - ✅ ページのフラットリスト表示(階層なし)
+  - ✅ クリックでページ遷移
+  - ✅ 選択状態の表示(aria-current)
+  - ❌ 展開/折りたたみ(階層構造なし)
+  - ❌ Drag & Drop(M3で追加)
+  - ❌ Rename/Duplicate/Delete(M3で追加)
+  - ❌ WIPフィルター(M3で追加)
+  - ❌ descendantCountバッジ(M3で追加)
+
+**データ取得**: モックデータで動作確認(100件程度)
+
+### 1.2. PageTreeSubstanceでの差し替え
+
+**変更ファイル**:
+- `src/client/components/Sidebar/PageTree/PageTreeSubstance.tsx`
+  - `ItemsTree` → `SimplifiedItemsTree`に置き換え
+  - propsも最小限に(targetPathOrIdのみ)
+
+### 1.3. 動作確認
+
+**確認項目**:
+- [ ] ページリストが表示される
+- [ ] クリックでページ遷移できる
+- [ ] 選択状態が表示される
+- [ ] **それ以外の機能は動かなくてOK**
+
+**工数**: 0.5日
+
+---
+
+## 📋 マイルストーン2: @headless-tree/react分析とAPI設計・Virtualization実装
+
+### 目的
+- @headless-tree/react の理解を深める
+- ライブラリの要件に合った最適なバックエンドAPIを設計
+- SimplifiedItemsTreeでvirtualizationを成功させる
+
+### 2.1. @headless-tree/react の調査・分析
+
+**調査項目**:
+- [ ] 公式ドキュメントの熟読(https://headless-tree.lukasbach.com/)
+- [ ] データ構造の要件理解
+  - ツリーデータの形式は?(flat vs nested)
+  - IDベースの参照か、オブジェクト参照か
+  - 展開/折りたたみ状態の管理方法
+- [ ] 非同期データローディングの仕組み
+  - `dataLoader` の実装パターン
+  - `asyncDataLoaderFeature` の使い方
+  - キャッシュ戦略
+- [ ] Virtualizationとの統合
+  - `@headless-tree/react` が提供するvirtualization機能
+  - または別途 `@tanstack/react-virtual` との統合方法
+- [ ] パフォーマンス特性
+  - 大量データ(5000件)での動作
+  - 展開/折りたたみのパフォーマンス
+
+**成果物**:
+- 調査レポート(.serena/memories/ に記録)
+- サンプルコード(動作検証用の小さな実装)
+
+**工数**: 1日
+
+### 2.2. バックエンドAPI設計
+
+**設計方針**:
+- 2.1の調査結果に基づいて、@headless-tree/reactに最適なAPI設計を行う
+- 既存API(`/page-listing/root`, `/page-listing/children`)との比較検討
+- 新APIが必要か、既存APIで十分かを判断
+
+**検討事項**:
+- エンドポイント設計(新規 or 既存活用)
+- レスポンス形式(flat配列 vs nested構造)
+- ページネーション・オフセット戦略
+- 展開状態の扱い(フロント管理 vs バックエンド管理)
+
+**成果物**:
+- API設計書(OpenAPIスキーマ案)
+- バックエンド実装方針
+
+**工数**: 1日
+
+### 2.3. バックエンドAPI実装
+
+**実装ファイル**:
+- `src/server/routes/apiv3/page-listing.ts`: エンドポイント追加
+- `src/server/service/page-listing/page-listing.ts`: ビジネスロジック追加
+- `src/server/models/openapi/page-listing.ts`: スキーマ定義(必要に応じて)
+
+**実装内容**: 2.2の設計に基づく
+
+**工数**: 1.5日
+
+### 2.4. フロントエンド: @headless-tree/react統合
+
+**実装内容**:
+- `@headless-tree/react` のインストール
+- SimplifiedItemsTreeでの統合
+- 新バックエンドAPIとの接続
+- 展開/折りたたみ機能の実装
+
+**変更ファイル**:
+- `src/client/components/SimplifiedItemsTree/SimplifiedItemsTree.tsx`
+- `src/stores/page-listing.tsx`: 新API用のSWRフック追加
+
+**工数**: 1.5日
+
+### 2.5. Virtualization実装
+
+**実装内容**:
+- @headless-tree/reactのvirtualization機能を利用
+- または @tanstack/react-virtual との統合(2.1の調査結果に基づく)
+- スクロールパフォーマンスの最適化
+
+**工数**: 1日
+
+### 2.6. 動作確認
+
+**確認項目**:
+- [ ] ツリー構造が表示される
+- [ ] 展開/折りたたみが動作する
+- [ ] クリックでページ遷移できる
+- [ ] **5000件でもスムーズにスクロールできる** 🎯
+- [ ] 選択状態が表示される
+
+**工数**: 0.5日
+
+**M2合計工数**: 6.5日
+
+---
+
+## 📋 マイルストーン3: 機能の段階的追加
+
+### 目的
+- M1, M2で削ぎ落とした機能を段階的に復活させる
+- 元の実装から必要な部分だけを移植
+
+### 3.1. 追加する機能(優先順)
+
+**Phase 3-1: 基本機能**
+1. WIPページフィルター: isWipPageShown propsと表示制御
+2. descendantCountバッジ: CountBadgeForPageTreeItem移植
+3. 初期スクロール: usePageTreeScroll実装
+
+**Phase 3-2: 操作機能**
+4. Drag & Drop: react-dndの統合
+5. Rename: RenameInput移植
+6. Duplicate: Modal連携
+7. Delete: Modal連携
+
+**Phase 3-3: リアルタイム更新**
+8. Socket.io統合: descendantCount更新
+9. Mutation連携: 各操作後のデータ更新
+
+### 3.2. 実装方針
+
+- 既存実装からコピー&ペースト
+- SimplifiedItemsTreeの構造に合わせて調整
+- 1機能ずつ追加 → 動作確認 → 次の機能
+
+**工数**: 3日
+
+---
+
+## 📋 マイルストーン4: デグレチェック
+
+### 目的
+- 元のItemsTreeと機能比較
+- 失われた機能があればM3へ戻る
+
+### 4.1. 比較テスト
+
+**テスト項目**:
+- [ ] すべての基本操作(M3で追加した機能)
+- [ ] パフォーマンス(5000件でスムーズか)
+- [ ] エッジケース(空ページ、権限なしページ等)
+
+### 4.2. デグレ修正ループ
+
+- デグレ発見 → M3へ戻って実装 → M4で再確認
+
+**工数**: 1日
+
+---
+
+## 📁 ファイル変更まとめ
+
+| マイルストーン | 新規 | 変更 | 合計工数 |
+|-------------|-----|------|---------|
+| **M1** 最小SimplifiedItemsTree | 4ファイル | 1ファイル | 0.5日 |
+| **M2** 調査+API+Virtualization | 0-3ファイル | 3-4ファイル | 6.5日 |
+| **M3** 機能追加 | 0ファイル | 2ファイル | 3日 |
+| **M4** デグレチェック | 0ファイル | 0ファイル | 1日 |
+
+**合計**: 工数11日
+
+---
+
+## 🔍 既存実装の分析結果
+
+### 現在のAPI構造(page-listing.ts:36-81)
+
+**エンドポイント**:
+1. `GET /page-listing/root` → ルートページ "/" のデータ
+2. `GET /page-listing/children?id={pageId}` → 直下の子のみ
+
+**IPageForTreeItem の構造**(最適化済み):
+```typescript
+{
+  _id: string
+  path: string
+  parent: string
+  descendantCount: number
+  revision: string
+  grant: PageGrant
+  isEmpty: boolean
+  wip: boolean
+  processData?: IPageOperationProcessData
+}
+```
+
+### 現在のフロントエンド構造
+
+**ItemsTree利用箇所**:
+- `PageTreeSubstance.tsx`: メインのページツリー(**ターゲット**)
+- `PageSelectModal.tsx`: ページ選択モーダル
+- `AiAssistantManagementPageTreeSelection.tsx`: AI Assistant設定
+
+**CustomTreeItem実装**:
+- `PageTreeItem.tsx`: メインツリー用(Drag&Drop、Rename等の全機能)
+- `TreeItemForModal.tsx`: モーダル用(簡素化版)
+
+**データフェッチング**:
+- `TreeItemLayout.tsx:50`: 各TreeItemが個別にSWRフック呼び出し
+- `useSWRxPageChildren()`: 子要素取得
+
+---
+
+## ✅ このプランの利点
+
+1. **M1が超高速**: 0.5日でPageTreeSubstanceの差し替え完了
+2. **M2が調査から始まる**: ライブラリの理解を深めてから設計・実装
+3. **柔軟な設計**: 調査結果に基づいて最適なAPI構造を決定
+4. **リスク最小化**: 各マイルストーンで「動くもの」ができる
+5. **既存コード保護**: ItemsTree、PageSelectModal等は一切変更なし
+6. **記録が残る**: 調査レポートを .serena/memories/ に保存
+
+---
+
+## 🚨 過去の失敗要因(教訓)
+
+### 前回の失敗原因
+1. **PageTreeItem や TreeItemLayout、CustomTreeItem によるレンダリングアイテム可換機能が複雑すぎて、デグレを誘発**
+   - 対策: SimplifiedItemsTreeで完全に切り離す
+
+2. **バックエンド API の分析が不十分なまま進めてしまった**
+   - 対策: M2.1で徹底的に @headless-tree/react を調査してから設計
+
+### react-window/react-virtual 失敗原因(前回プラン)
+1. **動的itemCount**: ツリー展開時にアイテム数が変化→react-windowの前提と衝突
+2. **非同期ローディング**: APIレスポンス待ちでフラット化不可
+3. **複雑な状態管理**: SWRとreact-windowの状態同期が困難
+
+**今回の対策**: @headless-tree/react でこれらの問題が解決できるか調査する(M2.1)
+
+---
+
+## 📝 プラン策定日
+
+2025-11-10
+
+## 📝 最終更新日
+
+2025-11-10