Skip to content

ファイル内容表示にてhtmlファイル レンダリング #490

@Kewton

Description

@Kewton

Note: このIssueは 2026-03-13 にレビュー結果(Stage 7: 影響範囲レビュー 2回目)を反映して更新されました。
詳細: dev-reports/issue/490/issue-review/

概要

ファイルパネルでHTMLファイルを選択した際に、ソースコード表示だけでなくブラウザレンダリング(プレビュー)表示を可能にする。
MarkdownEditorと同様に、ソースコード編集とプレビューの分割表示を提供する。

背景・課題

  • Claude等のAIツールがHTMLファイルを生成するケースが増えている(ゲーム、データ可視化、レポート等)
  • 現状、HTMLファイルはテキストとしてシンタックスハイライト表示されるのみで、レンダリング結果を確認するにはブラウザで別途開く必要がある
  • 単一HTMLファイルで完結するインタラクティブコンテンツ(スペースインベーダー等のゲーム、Canvas描画)をCommandMate内で直接操作したい
  • CommandMateのファイルパネル内で直接プレビュー・操作できれば、開発ワークフローが効率化される

提案する解決策

レンダリング方式

  • iframe + sandbox属性を使用
  • MARPスライドとの違い: MARPはサーバーサイドAPI(POST /marp-render)でMarkdownをHTMLに変換した結果をiframe srcDocに設定するが、HTMLプレビューではサーバーサイドAPIは不要。既存のファイル取得API(GET /files/...)が返すHTMLテキストをそのままiframe srcDocに設定するクライアントサイドのみのパターンで実装する
  • sandbox属性のレベルをトグルで段階的に切り替え可能
  • CSP設定の変更が必要: next.config.js のCSPヘッダーに frame-src 'self' blob: を追加する必要がある。srcdoc 属性を使用したiframeはブラウザによっては blob: originとして扱われるため、明示的なCSP設定がないとブロックされる可能性がある(S3-001)

サンドボックスレベル(3段階トグル)

レベル sandbox属性 スクリプト 外部リソース ユースケース
Safe(デフォルト) sandbox="" ブロック ブロック 静的HTMLプレビュー
Interactive sandbox="allow-scripts" 許可 ブロック 単一HTMLゲーム・Canvas等
Full sandbox="allow-scripts allow-same-origin" 許可 許可 外部CSS/画像参照あり
  • デフォルトは Safe(最も安全)
  • ユーザーが明示的にレベルを上げることでスクリプト実行・外部リソース読み込みを許可

Fullレベルのセキュリティリスク

allow-scripts + allow-same-origin の組み合わせは、iframe内のスクリプトが親ページのDOMやCookieにアクセス可能になるため、sandbox属性を実質無効化する危険な組み合わせとして知られている(MDN Web Docs参照)。

対策:

  • Fullレベル選択時に警告ダイアログを表示し、ユーザーに明示的なリスク承認を求める
  • 初期実装ではSafe/InteractiveレベルはsrcdocでMVPを実装する。Fullレベルもまずsrcdocで実装し、セキュリティテストの結果に応じてblob: URLへのorigin分離方式への移行を検討する(S5-004)
  • 将来的にFullレベルの廃止も視野に入れ、allow-scriptsのみの2段階構成への簡素化を検討する

表示モード

MarkdownEditorと同様の3モード:

  • ソースのみ: HTMLソースコードをシンタックスハイライト表示(現状の動作)
  • プレビューのみ: iframe内でHTMLをレンダリング表示
  • 分割表示: 左にソース、右にプレビュー

モバイル版での表示モード

モバイル版(FileViewer)ではスペースの制約から分割表示は行わず、MarkdownEditorのmobileTab方式と同様にタブ切り替え(ソース/プレビュー)で実装する。FileViewer.tsxにはタブ切り替えUIの既存基盤がないため、新規実装が必要となる(S3-005)。iframeの高さはMARPプレビュー同様に isFullscreen の有無で切り替える。

HTMLファイル編集方針

HTMLファイルの編集UIは新規の HtmlPreview.tsx コンポーネントで実装し、既存の MarkdownEditor.tsx は再利用しない。MarkdownEditorはMarkdown固有のツールバーやプレビューロジックを含んでおり、HTMLプレビューとは要件が異なるため、別コンポーネントとして独立させる(S3-006)。

主要な変更点

  • HTMLファイル判定ロジックの追加(.html, .htm
  • FilePanelContent / FileViewer にHTML用レンダリング分岐を追加
  • サンドボックスレベル切り替えトグルUI(Safe / Interactive / Full)
  • HTMLファイルの編集対応(editable-extensionsへの追加)
  • CSPヘッダーへの frame-src ディレクティブ追加(next.config.js

実装タスク

  • src/config/html-extensions.ts 新規作成
    • HTML_EXTENSIONS 配列(.html, .htm
    • isHtmlExtension() 判定関数
    • HTML_MAX_SIZE_BYTES 定数(5MB)
    • 注: image-extensions.tsほど複雑な構造(magic bytes検証等)は不要。テキストベースのためシンプルなAPIで良い
  • src/config/editable-extensions.ts.html, .htm を追加
    • EDITABLE_EXTENSIONS 配列への追加
    • EXTENSION_VALIDATORS 配列にHTMLファイル用エントリを追加(maxFileSize: 5MB、additionalValidation: 初期実装ではundefined。sandbox属性による隔離で安全性を担保するため、保存時のXSSパターン検出は不要。将来的にXSS危険パターン検出を追加する可能性あり)(S5-003)
    • 重要: EDITABLE_EXTENSIONSEXTENSION_VALIDATORS は必ず同時に追加する(アトミックな変更)。片方のみ追加すると保存APIが Unsupported extension エラーを返す(S3-004)
  • src/types/models.tsisHtml?: boolean フラグ追加(JSDocコメント付き: /** Whether the file is an HTML file (optional) - Issue #490 */。既存のisImage/isVideoと同じコメント規約に従う)(S5-001)
  • src/app/api/worktrees/[id]/files/[...path]/route.ts のGETハンドラ変更
    • HTMLファイルは既存のテキスト読み込みロジックで処理する(バイナリ変換は不要)
    • 非画像・非動画パスにおいて、拡張子がHTML拡張子の場合にレスポンスに isHtml: true フラグを追加する
    • html-extensions.tsの isHtmlExtension() をimportして判定
    • NextResponse.json のレスポンスオブジェクト(行290-296付近)に isHtml: isHtmlExtension(extension) プロパティを追加する(S7-001)
  • src/components/worktree/HtmlPreview.tsx 新規作成(iframe sandbox + サンドボックスレベルトグル)
    • Fullレベル選択時の警告ダイアログ実装
  • src/components/worktree/FilePanelContent.tsx にHTML用表示分岐追加(ソース/プレビュー/分割)
  • src/components/worktree/FileViewer.tsx にHTML用表示分岐追加
    • モバイル版: タブ切り替え方式(ソース/プレビュータブ)の新規実装(S3-005)
    • canCopy 判定: isHtml を除外条件に追加する必要はない(HTMLはコピー可能)が、プレビューモード時の動作を確認する
    • codeViewDatauseMemo: isHtml 条件を追加し、HTMLプレビュー表示時は不要なシンタックスハイライト処理をスキップする(S3-003)
    • renderContent(): isHtml 分岐を isVideo/isImage の後、codeViewData 参照の前に追加する(S3-003)
  • next.config.js のCSPヘッダーに frame-src 'self' blob: を追加(S3-001)
    • 開発環境でiframe srcDocが正常に動作することを確認する
  • 既存テストの更新: tests/unit/config/editable-extensions.test.ts(S3-002)
    • テスト should only include .md for nowshould include .md, .html, .htm extensions に更新し、EDITABLE_EXTENSIONSの長さを3に変更
    • EXTENSION_VALIDATORS のテストにHTMLエントリの検証を追加
  • ユニットテスト追加(HTML拡張子判定、サンドボックスレベル切り替え、EXTENSION_VALIDATORS検証)

受入条件

  • .html / .htm ファイルがプレビュー表示される
  • ソース/プレビュー/分割の3モード切り替えが動作する(PC版)
  • モバイル版ではタブ切り替え(ソース/プレビュー)で動作する
  • サンドボックスレベル3段階(Safe / Interactive / Full)の切り替えが動作する
  • Safe: スクリプト・外部リソースが完全ブロックされる
  • Interactive: インラインスクリプトが実行される(単一HTMLゲーム等が操作可能)
  • Full: 外部CSS/画像/フォントが読み込まれる。選択時に警告ダイアログが表示される
  • 5MBを超えるHTMLファイルはプレビュー表示せず、ソースコード表示のみとする
  • HTMLファイルの保存がEXTENSION_VALIDATORSのバリデーションを通過する
  • PC版(FilePanelContent)とモバイル版(FileViewer)の両方で動作する
  • CSPヘッダーにframe-srcが設定され、iframe srcDocが開発環境で正常に動作する
  • 既存テスト(editable-extensions.test.ts)が更新され、全テストがパスする
  • FileContentResponse型にisHtmlが含まれ、クライアント側で型安全にアクセスできること(S5-002)

影響範囲

変更対象ファイル

ファイル 変更内容
src/config/html-extensions.ts 新規: HTML拡張子定義(HTML_EXTENSIONS, isHtmlExtension, HTML_MAX_SIZE_BYTES)
src/config/editable-extensions.ts .html, .htm 追加 + EXTENSION_VALIDATORSにHTMLエントリ追加
src/types/models.ts isHtml フラグ追加(JSDocコメント付き - Issue #490)(S5-001)
src/app/api/worktrees/[id]/files/[...path]/route.ts GETハンドラのNextResponse.jsonレスポンスオブジェクトに isHtml: isHtmlExtension(extension) を追加(S7-001)
src/components/worktree/HtmlPreview.tsx 新規: HTMLプレビューコンポーネント(Fullレベル警告含む)
src/components/worktree/FilePanelContent.tsx HTML用表示分岐追加
src/components/worktree/FileViewer.tsx HTML用表示分岐追加(モバイル: タブ切り替えUI新規実装、codeViewData isHtml条件追加)
next.config.js CSPヘッダーに frame-src 'self' blob: を追加(S3-001)
tests/unit/config/editable-extensions.test.ts 既存テスト更新: EDITABLE_EXTENSIONS長さ変更、HTML EXTENSION_VALIDATORS検証追加(S3-002)
tests/unit/config/html-extensions.test.ts 新規: HTML拡張子判定テスト
tests/unit/components/html-preview.test.ts 新規: HTMLプレビューコンポーネントテスト

セキュリティ考慮

  • iframe sandbox属性による段階的セキュリティ制御
  • デフォルトは最も安全なレベル(Safe: sandbox="")
  • スクリプト許可時もallow-top-navigationは付与しない(親ページへの影響を防止)
  • CSP frame-src設定: next.config.jsframe-src 'self' blob: を追加し、srcdoc iframeの動作を許可する(S3-001)
  • Fullレベル(allow-scripts + allow-same-origin)のリスク: sandbox属性が実質無効化される。選択時に警告ダイアログを表示し、blob: URLによるorigin分離を検討する
  • DOMPurify方針: iframe srcDoc方式ではDOMPurifyによるサニタイズは行わず、sandbox属性による隔離で安全性を担保する。DOMPurifyはSafeモードでのフォールバック表示(iframeなしのインラインHTML表示)を将来検討する場合のオプションとして位置づける
  • 初期実装のiframe方式: 全レベル(Safe/Interactive/Full)でsrcdoc方式を採用。Fullレベルはセキュリティテスト結果に応じてblob: URL方式への移行を検討する(S5-004)
  • ファイルサイズ上限: 5MB(5MBを超えるHTMLファイルはソースコード表示のみ)
  • EXTENSION_VALIDATORSによるHTMLファイル保存時のバリデーション(additionalValidation: 初期実装ではなし)(S5-003)

参考実装

  • MARPスライドレンダリング: src/app/api/worktrees/[id]/marp-render/route.ts(iframe srcDoc パターン。ただし本Issueではサーバーサイド変換APIは不要)
  • MarkdownEditor: src/components/worktree/MarkdownEditor.tsx(ソース/プレビュー分割パターン、モバイルタブ方式。ただし本IssueではMarkdownEditorを再利用せず、HtmlPreview.tsxとして独立実装する)
  • SVG XSS防止: src/config/image-extensions.ts(危険パターン検出)
  • ファイルバリデーション: src/config/editable-extensions.ts(EXTENSION_VALIDATORS参照実装)

レビュー履歴

ステージ 日付 概要
Stage 1-2 2026-03-13 通常レビューの指摘事項反映(DOMPurify方針明記、Fullレベルリスク記載等)
Stage 3-4 2026-03-13 影響範囲レビューの指摘事項反映(CSP設定追加、既存テスト更新、FileViewer codeViewData対応、モバイルタブUI、MarkdownEditor非再利用方針)
Stage 5-6 2026-03-13 通常レビュー2回目の指摘事項反映(models.ts JSDocコメント規約、FileContentResponse型安全性テスト追加、EXTENSION_VALIDATORS additionalValidation方針明確化、srcdoc/blob: URL段階的方針)
Stage 7-8 2026-03-13 影響範囲レビュー2回目の指摘事項反映(GETハンドラのNextResponse.jsonレスポンスオブジェクトへのisHtml追加の具体的変更ポイント明記)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions