Files
ketle-man-comfyui-comic-cre…/DEVLOG.md
T

866 KiB
Raw Blame History

DEVLOG — comfyui-comic-creater


2026-10-22(MOSS-Image タブ: 吹き出しなしの単独テキストの取込、panelラベルの使い分け、空の文字枠の送信除外、未リリース)

1. 吹き出しなしの単独テキストの取り込み(新機能)

  • 「フキダシ・セリフを取り込む」に、チェックボックス「吹き出しなしの文字も取り込む」(既定オン)を追加。テキストツールで置いた <text> を文字枠として取り込む。フキダシ内のテキスト(g.balloon-shape の子)と defs / clipPath / mask / pattern 内のテキストは対象外。
  • 回転・拡縮・せん断・縦書きを含む実際の見た目の範囲が要るため、文字だけの新しいSVGを画面外(position:fixed; left:-100000px)へ描画し、getBoundingClientRect() で実測する(extractFreeTexts)。document.fonts.ready を待ってから測る。枠は実測範囲+フォントサイズの0.15倍の余白で、fitBox=false(文字を編集しても枠が縮まない)。フォントサイズは font-size × data-scale-x/y の幾何平均 × キャンバス倍率。縦書きは style の writing-mode。
  • caption は文字数による簡易な推定(6文字以下=Sound effect.、それ以外=Narration text.)。回転した文字は外接矩形になる(MOSS-Imageの枠は回転を表せない)。
  • 再取込は、取り込み済みの枠(src: 'balloon' / 'text')だけを入れ替える。チェックを外して取り込むと、前回の単独テキストの枠は残る。

2. セキュリティ修正(取込の途中で発見・修正)

  • 初版は保存済みページのSVG文字列を innerHTML で画面外のDOMへ入れていたため、保存済みSVGに紛れた onload / onerror 等が実行されうる(旧方式では実際に実行されることを確認)。まず DOMParser + sanitizeSvgTree() に変えたが、禁止リスト方式では名前空間付きの要素(<s:script>)、<set> / <animate>、style 内の url() 等の抜け道を塞ぎきれない。
  • 最終形は許可リスト方式: 保存済みSVGは描画せず(パースしただけのDOMは何も実行しない)、<text> / <tspan> と許可リストの属性(x y dx dy font-* letter-spacing text-anchor transform data-scale-*)・許可プロパティだけの style・祖先 <g> の transform(matrix/translate/scale/rotate/skew の形式のみ)を、新しく作ったSVGへ写して測る。値は括弧・;・:・/ を通さない。攻撃用SVG(名前空間付き script、XHTMLの iframe srcdoc、set / animate、javascript: のリンク、style 内の url())で、スクリプトの実行0件・外部通信0件を確認した。

3. 枠固定の文字の表示

  • 取り込んだフキダシ・単独テキスト(fitBox=false)の表示を、枠の幅で折り返す(white-space: pre-wrap)。実ページの思考フキダシの長い英文が、枠の外へ1行ではみ出して見えていたため。MOSS-Image も枠の中で折り返す。単独テキストは左上(縦書きは右上)から置く。

4. panel ラベルの使い分けと、空の文字枠の送信除外

  • 描画種別・種別セレクトに「コマ」(panel ラベル)を追加(黄色)。コマ取込の panel ラベル化はチェックボックス(既定は object)。panel は学習時にあるラベルだが、公開仕様は object / character / text のみ。32枚(8シード×4通り)の測定で、object との差は誤差の範囲内(枠線・ガターの再現度)、summary.canvas: "multi_panel" も同様だった。ユーザーが使い分けられるように選べる形にした。
  • 文字が空(空白のみ)の text item は、_moss_layout_prompt で除外する。送ると、モデルがその領域を「文字を書く領域」と解釈して、罫線入りの紙のようなものを描いてしまう(8シード中8)。公式コンパイラも空の text を拒否する。UIでは破線で薄く表示する(入力のたびに切り替わる)。

5. 調査(実装なし)

  • MOSS-Image の bbox: 公式仕様では、items のキーは label / caption / bbox / text / tags / palette のみ、bbox は軸並行の長方形(4つの数値)。回転角・形状(多角形)の項目はない(公式コンパイラは未知のフィールドを拒否)。非公式の rotation 項目は無視される。caption の英文での回転指示は、回転後の外接矩形と併用すると傾くが、文字が崩れやすい。形状は caption の英文で表現する。
  • 参照画像ノード ThetaCursed/ComfyUI-MOSSImageTextEncoder(Qwen3.5の視覚タワー経由の参照画像条件づけ)を実機で検証(導入はコミット 289170a 固定。CC本体は未使用): エラーなく動き、参照なしは標準と画素同一、時間・VRAM問題なし。レイアウトなしでは参照が構図を乗っ取る(シート全体を渡すとシートそのものが出る)。レイアウトJSON併用なら構図は保たれるが、全身の参照は近景のコマに白背景が漏れる。単純なデザインでは文章の外見説明で参照と同等に揃う。このため「キャラ設定(文章)の登録・自動挿入」を先に検討したが、今回は見送り(スクリプトタブの要素・Autoタブの脚本との関係を整理してから再検討)。

6. 検証

  • 実ページ(ユーザーの保存済み45ページ)で、フキダシ168件・単独テキスト8件を、エラー・異常値なしで抽出できた。実ページ「これはTEST_p1_1791201461030」の取込結果(位置・大きさ・フォントサイズ)を元データと突き合わせて一致を確認し、取り込んだレイアウトで生成して、縦長の吹き出し・思考フキダシ(英文)・擬音「あ」が指定の位置に描かれた。
  • 未確認: 英語・中国語UIの表示、擬音かナレーションかの推定の精度、回転した単独テキストの実ページでの見た目。
  • py/ccc.py を変更したため、反映にはComfyUIの再起動が必要(再起動後に、panel ラベルの通過と空の文字枠の除外をAPIで確認済み)。

How to apply

  • ページのSVG文字列を、保存済みでも外部由来でも、innerHTML でDOMへ入れない。DOMへ描画する必要があるなら、許可リスト方式で必要な要素・属性だけを新しいDOMへ写す。禁止リスト方式(sanitizeSvgTree)は、名前空間付きの要素やアニメーション要素を塞ぎきれない。
  • 外部モデルの仕様外の入力(空の text など)は、「無視されるだけ」と仮定せず、同じシードで有無を比べて確かめる。
  • 回転・拡縮を含む <text> の範囲は、数式ではなく、画面外に描画して getBoundingClientRect() で測るのが確実(document.fonts.ready を待つ)。
  • 再取込できる要素には src のような印を付け、再取込では印の付いたものだけを入れ替える。

2026-10-22(MOSS-Image タブ: シードの動作モード、フキダシ・セリフの自動取込、シード欄のレイアウト変更、v1.58.1)

1. シードの動作モード(修正・改善)

  • 従来は、シードが -1(ランダム)のとき、生成のたびに使われたシード値が入力欄へ書き戻されていた。そのため次の生成からは固定シードになり、ランダムのままにするには毎回 -1 へ戻す必要があった。
  • 「ランダム(既定)/固定/生成ごとに+1」を選べるようにした。ランダムのときは入力欄の値に関係なく毎回 -1 を送る(入力欄は無効表示)。固定は入力欄の値。「+1」は入力欄の値から始め、生成後に使った枚数ぶん進める(入力欄が負のときは初回だけランダム)。
  • ランダムで使われたシードは「直前のシード」に表示し、「このシードを固定」で固定モードへ切り替えられる(気に入った結果の再現用)。コマ単位モードは「元のシード+コマの順番」で、直前のシードには最初のコマの値を出す。モードは localStorage に保存。
  • 右パネルのシード欄は 1 行目=「シード:」、2 行目=モードと値、3 行目=直前のシード(左)と固定ボタン(右寄せ)の 3 行構成にし、生成の状態表示(「生成完了」など)をシード欄の下へ移した。

2. フキダシ・セリフの自動取込(新機能)

  • レイアウトエディタに「フキダシ・セリフを取り込む」ボタンを追加。ページのレコード(dbGet('pages', …))のオーバーレイ(overlaySvgContent)と各コマ(panelSvgContent)から g.balloon-shape を取り出し、セリフ(data-bubble-text)が入っているものを「文字」枠として取り込む。
  • 位置・大きさは data-cx/cy/rx/ry(ページのSVG座標)から、コマ取込と同じ換算(キャンバス/ページ)で作る。枠はフキダシの輪郭。フォントサイズは data-font-size-svg × 縦の倍率、縦書きは data-bubble-text-vertical、形は data-shape-type を caption の英文(Speech bubble. / Thought bubble. / Shouting burst-shaped bubble. など)へ変換する。読み順(上から下、同じ段は右から左)に並べる。
  • 取り込んだ枠には src: 'balloon' を付け、再取込では取り込み済みの枠だけを入れ替える(手で足した枠は残す)。キャンバスの縦横比がページと違うときは、ページに合わせて既存の枠も拡縮する(確認ダイアログ)。
  • 文字枠に fitBox を追加し、「選択中の要素」に「枠を文字の大きさに合わせる」を追加した。取り込んだ枠は fitBox=false(文字を編集しても枠が縮まない。表示は枠の中央)。autoSizeText と手動リサイズ後のフォントサイズ更新は fitBox=false では行わない。
  • 単独のテキスト要素(吹き出しなしの文字)は対象外(今後の検討)。コマ単位モードでは使われない。

3. 検証

  • テスト用のページレコードを IndexedDB へ書き込み、縦書き・思考・爆発の 3 種のフキダシを取り込んで、座標・フォントサイズの換算、セリフなしのスキップ、再取込の入れ替え(手で足した物体が残る)、fitBox の切替を Playwright で確認した。取り込んだレイアウトで生成すると、縦書きの吹き出しと叫びの吹き出しが指定の位置・大きさで描かれた。
  • シードは、ランダム(2 回とも -1 送信、入力欄は不変)、固定(同じシード)、+1(100 → 101)を実機生成で確認した。
  • 未確認: 実際に使っているページでの取込、英語・中国語 UI の表示、コマ単位モードでのシード(コマごとの値)。

How to apply

  • 「ランダムなら毎回違う値」の機能で、使った値を入力欄へ書き戻すと次回から固定になる。入力欄は「指定する値」専用にし、使った値は別の表示(直前のシード)に出す。
  • 取り込み元を持つ要素には src のような印を付け、再取込では印の付いたものだけを入れ替える。手編集した要素を消さずに済む。
  • 枠のサイズを内容から自動決定する要素でも、外部の形(フキダシの輪郭など)に合わせたい場合は自動サイズを切れるフラグ(fitBox)を持たせる。

2026-10-21(MOSS-Image タブ(ローカル生成・レイアウトエディタ)を追加、v1.58.0)

1. MOSS-Image タブ(新機能)

  • ComfyUI 本体がネイティブ対応した画像生成モデル MOSS-Image(OpenMOSS、5B DiT、テキストエンコーダ Qwen3.5-9B、VAE FLUX.2、Apache-2.0)を、専用タブ「MOSS-Image」からローカル生成する。Comfy クレジット・外部 API キーは不要。日本語を含む文字の描画に強く、物体・キャラ・文字の位置を bbox で指定できる。
  • 画面は 3 カラム: 左=レイアウトエディタ、中=設定パネル(プロンプト / JSON / モデル・サンプラー の上部タブ。プロンプトタブ最上段の「生成対象」は <details> で折りたたみ可)、右=生成・結果・配置。
  • 生成モードは「1 頁まるごと」(ページ全面のオーバーレイへ配置)「コマ単位」(各コマの縦横比から約 1MP で生成し、insertImage の placement と preserveAspectRatio:'none' でコマへストレッチ配置)「自由サイズ」(生成のみ)。
  • 「現在のページのコマを取り込む」でコマ枠を物体の枠として取り込み(キャンバスはページの縦横比・約 2MP に自動調整、caption に位置表現)。設定とレイアウトは localStorage(ccc_moss_v1)に保存。

2. バックエンド(py/ccc.py)

  • _moss_workflow で API 形式のワークフロー(UNETLoader →(LoraLoaderModelOnly)→(SkipLayerGuidanceDiTSimple 層 22・0〜0.5)→ KSampler、CLIPLoader(type=moss_image)、VAELoader、EmptyFlux2LatentImage、SaveImage(cc_moss/MOSS))を組み立て、Nanobanana のネイティブ経路と同じ方式で /prompt へ直接投入する。ワークフロー JSON ファイルは持たない。
  • _native_nb_run に出力ノード ID・client_id・タイムアウトを引数化し、API キーが空ならパートナー認証を付けない(従来の呼び出しは既定値で同じ動作)。
  • ルート POST /api/ccc/moss/generate(生成。画像本体は返さず /view 用の参照のみ)と POST /api/ccc/moss/layout-prompt(レイアウト → JSON のプレビュー用)。モデル一覧は ComfyUI 自身の /object_info から取得する。
  • レイアウト JSON は ComfyUI 標準の MOSSImageLayoutPrompt と同じ形式(summary / items(label・caption・text・bbox)/ bbox_coord_space=abs_centralized / bbox_format=yxyx。bbox はキャンバス中心原点のピクセル [y0,x0,y1,x1])を _moss_layout_prompt で生成する。

3. レイアウトエディタと文字枠

  • 描画種別(物体=緑・キャラ=ピンク・文字=水色)をボタンで選び、ドラッグで枠を描く。前面へ/背面へ/削除、Delete キー、枠の縁・番号・選択中の枠の内側のドラッグで移動、角で拡縮。
  • 未選択の枠の内側のドラッグは「その上に新しい枠を描く」(クリックのみなら選択)、Shift+ドラッグは常に描画。枠の内側を常に移動にすると、コマ(物体)の上にセリフ枠を置けなかったための仕様。
  • 文字枠は描いた直後に枠内へ直接入力(Escape / Ctrl+Enter で確定)。フォントサイズ(画像上の px)を持ち、枠は入力した文字の大きさになる(全角 1em・半角 0.55em・行間 1.2・余白 0.3 倍の近似)。手動リサイズすると、その枠に収まる最大のフォントサイズへ更新される。
  • 縦書き: 「文字」の右隣の横書き/縦書きトグルは新しく描く文字枠の既定のみ(選択中の枠には作用させない。作用させると、描いた直後に切り替えるとその枠が書き換わるため)。描いた後は「選択中の要素」の書字方向で変更する。縦書きは改行=列(右→左)として表示・入力・サイズ見積もりを縦組みにする。MOSS-Image のレイアウト形式に書字方向の項目は無いため、縦書きの文字枠は caption の末尾に Vertical text, written top to bottom, columns from right to left. を自動付与する。この指示だけで縦書きに描かれることを実機生成で確認した。

4. 検証

  • API 経由で 832×1216・25step・日本語セリフ入りの 2 コマレイアウトを生成(約 35 秒)し、コマ構図・セリフ・擬音が指定位置に描画された。縦書き指定を含むレイアウトでも、縦書き(右→左の列)で描画された。UI の生成ボタンから DMD 8step でも生成できた。
  • 枠の描画・移動・拡縮・クリック選択・文字の直接入力と再編集・フォントサイズ変更・縦書き切替・3 カラムとタブ切替・リロード後の復元を Playwright / Kapture で確認。
  • 未確認: ページのコマ取り込みの実機動作、コマ単位/1 頁モードでの配置、英語・中国語 UI の表示、画面幅 1500px 未満の折り返し表示、指定フォントサイズと実際の文字サイズの一致、縦書きの安定性(1 回の生成のみ)。

5. 未対応・検討

  • フキダシ・セリフ(g.balloon-shape の dataset.bubbleText など)の自動取込、Auto タブの画像生成エンジンへの追加は未実装(PLAN_backlog.md に記載)。
  • ヘルプ(日英中)・README(日英中)に MOSS-Image の節を追加した。

How to apply

  • キャンバス上のドラッグ操作で pointerdown に preventDefault() を入れると、ブラウザの dblclick が発火しないことがある。二度押しは pointerdown の時刻と対象で自前判定する。
  • 枠の上に別の要素を描きたいキャンバスでは、「枠内のドラッグ=移動」を固定にせず、選択状態と Shift で描画と移動を切り分ける。
  • py/ccc.py に新しいルートを足したときは、ComfyUI の再起動後でないと 404 になる(JS・HTML・CSS はリロードで反映)。
  • レイアウト形式に無い指示(縦書きなど)は、caption の英文で渡すと効く場合がある。

2026-10-17(動画ツールの移動モードとオーバーレイのはみ出し修正、テキスト・描画図形の保存抑制、v1.57.0)

1. 動画ツール: 移動モード(✥)を追加

  • 動画サブタブでは動画オブジェクトをクリックするだけでオーバーレイのプレビュー状態になり、位置を動かすには他のツールへ切り替える必要があった。再生ボタン(▶)の左隣に ✥ ボタンを追加。ON中は _videoSuspendOverlay() でオーバーレイを透明・クリック透過にして動画だけ止め、下の <image> を通常の画像と同じようにドラッグ移動・リサイズ・回転できる。ON中は動画オブジェクトをクリックしてもオーバーレイは出ない(_ccVideoOnObjectSelected で抑止)。OFFにすると移動後の位置でオーバーレイを再表示する。状態は state.video.moveMode。

2. 動画オーバーレイがページ上端からはみ出してツールメニューに重なる問題(修正)

  • 原因: オーバーレイ(z-index:150)は動画オブジェクトの表示矩形をそのまま使うため、ページ端からはみ出した動画では直上のツールメニューへ食い込み(実機再現で約100px)、クリックを奪っていた。
  • 実装: _videoSyncPosition で、プレビュー領域(#layout-preview)の範囲に clip-path: inset(...) でクリップする。

3. テキスト・描画図形のクリックだけの保存を抑制

  • v1.56.0 で画像・フキダシ・グループに入れたものと同じ。テキスト(09e-text-tool.js)と描画図形(17c-layer-draw-handles.js)も、実際に動かした場合だけ savePanelSvg / saveOverlaySvg を呼ぶ。クリック選択だけでは全SVGの複製・直列化・DB保存を走らせない。
  • 描画図形のドラッグには pushHistory(Ctrl+Z 用履歴)が無い(未対応)。

How to apply

  • 絶対配置のオーバーレイをSVG上の要素に重ねる機能は、プレビュー領域でのクリップを必ず入れる(はみ出し対象が他のUIを覆わないように)。
  • ドラッグ系の mouseup で保存する処理は、「動かしたか」のフラグで保存を分ける。

2026-10-17(レイアウトタブの操作遅延の解消、テキスト角ハンドルの拡縮方向、フキダシ内テキストの位置ドラッグ、v1.56.0)

1. レイアウトタブでオブジェクトの操作が遅い・レイヤーパネルの選択が効かない(修正)

  • 原因: 3Dポーズ画像の要素に data-pose3d-model(VRMモデルのbase64、1枚あたり約56MB)が埋め込まれており、実機のページでSVG全体が約114MBになっていた。クリックしただけでも mouseup で savePanelSvg(SVG全体の複製・直列化・IndexedDB保存)が走るため、次の操作が詰まり、保存完了時の renderLayerPanel() でクリック中のレイヤー行が差し替わって click が届かなかった。
  • 実装: 23-pose3d-bridge.js でモデルを埋め込まないようにした(読み出し側は元々無く、再編集はポーズJSON data-pose3d-pose のみで足りる)。renderLayoutTab でページを開く際に既存の data-pose3d-model を正規表現で除去してDBへ書き戻し(_stripPose3dModelFromPage)、savePanelSvg でも保存時に除去する。実機でSVGが約114MB→約1.1MBに低下。
  • 画像・フキダシ・グループのドラッグ処理(08-panels-images.js、06a-polygon-geometry.js)は、実際に動かした場合だけ保存する(クリック選択のみでは保存しない)。
  • 未対応: テキスト・描画図形のドラッグにも同種の構造が残っている可能性がある(未調査)。

2. テキストの角ハンドルのリサイズ方向(変更)

  • 従来は中心からの距離比で拡縮していたため、どの角をドラッグしても同じ動きだった。反対側の角を固定し、固定角→ドラッグ角の対角線上へカーソルを射影した比でフォントサイズを決める方式に変更(09e-text-tool.js)。拡縮でずれた固定角はテキストごと平行移動して戻す。回転・変形したテキストでも変形後の角座標(_textLocalToSvg)で扱う。辺ハンドルの縦横伸縮・回転は従来どおり。

3. フキダシ内テキストの位置をドラッグで移動(新機能)

  • テキストを内包したフキダシの選択時、テキストの周りに紫の点線ボックスを表示し、左下の三角コーナーのドラッグでテキストだけを動かせる。ダブルクリック(三角コーナー・フキダシ)で従来のモーダル。
  • 位置は data-text-offset-x/y(フキダシのローカル座標での寄せ位置からの追加移動量)に保存し、_bubbleTextRenderText で全tspanへ加算する。回転したフキダシでも向きに沿って動く。モーダルで上下寄せ・文字寄せを変えるとオフセットは解除(寄せ位置を優先)。
  • 点線ボックス(.balloon-textbox)と三角コーナー(.balloon-textbox-mark)は編集用の表示で、保存用クローン(savePanelSvg / saveOverlaySvg)から除外する。実装は 09c-balloon-handles.js(updateBubbleTextBox)、ドラッグ処理は 09d-balloon-tools.js。動かしたときだけ履歴・保存を行う。

How to apply

  • 保存が重いと感じたら、まず svg.outerHTML.length と、巨大な属性(data-*)を持つ要素がないかを疑う。
  • 新たに要素へ大きなデータ属性を埋め込む機能を作る場合は、SVGへ永続化する前に別ストアへ逃がすこと。

2026-10-14(Nanobanana: 生成方式を設定タブで切替し、半自動マンガ・Autoタブにも適用、v1.55.0)

v1.54.0 で Nanobanana タブにだけ追加した「ComfyUIネイティブノード」方式を、共通の設定として設定タブで切り替えられるようにし、半自動マンガ(スクリプト)と Auto タブの Nanobanana 生成にも適用した。3か所とも requestNanobananaGenerate(payload)(base64画像の配列を返す共通の入口)を通るため、呼び出し側はほぼ無改修。

UI

  • 設定タブに「Nanobanana 設定」(settings-nanobanana-engine)を追加。選択は localStorage(ccc_nanobanana_engine)に保存し、Nanobanana タブの選択欄と ccc-nanobanana-engine-changed イベントで連動する。接続状態(Gemini キー / COMFY_API_KEY)も表示。
  • nanobanana.js: getNanobananaEngine / setNanobananaEngine を追加・export。checkNanobananaKeyStatus(半自動マンガのモーダルでも使用)と requestNanobananaGenerate が設定に従う。

バックエンド(ccc.py)

  • POST /api/ccc/nanobanana/native/generate に return_b64 を追加: ワークフローの出力を SaveImage ではなく PreviewImage(temp、再起動で消える)にし、結果を /view から base64 で返す。呼び出し側が自前の保存先(cc_nanobanana / cc_auto)へ保存するため、ComfyUI の output に重複ファイルを残さない。PNG の prompt / workflow 埋め込みは SaveImage と同様に付くので、保存先のPNGにもメタデータが残る。
  • モデル名の対応: 半自動マンガ・Auto は Gemini の ID で指定してくるため、gemini-nano-banana-2.1 → Gemini Nano Banana 2.1、gemini-3.1-flash-image → Nano Banana 2 (Gemini 3.1 Flash Image)、gemini-3.1-flash-lite-image → Nano Banana 2 Lite に対応づけ。gemini-3-pro-image(Nano Banana Pro)はネイティブでは別ノード(GeminiImage2Node)のため 未対応(nanobanana_native_unsupported_model)。Lite の 2K/4K はエラー(黙って1Kに丸めない)。エラー文言は3言語。

実機確認(ComfyUI_5 / ポート8189、v0.40.0)

  • 設定タブの切替が Nanobanana タブ(選択欄・モデル欄・接続状態)に連動すること、Pro 未対応・Lite の 2K・不正モデルのエラー文言が出ることを確認(課金なし)。
  • 半自動マンガと同じ形(参考画像1枚付き I2I、gemini-nano-banana-2.1)で共通の入口を実呼び出し: 18.4秒、base64 の PNG(848×1264、prompt / workflow チャンク入り)が返り、output/cc_nanobanana_native/ に重複は増えない(プレビューと参考画像は temp)。
  • Auto タブの generateAutoImage は、生成リクエストをスタブにして、設定に応じて /native/generate(return_b64: true)と従来の /nanobanana/generate が切り替わり、どちらも save-auto-image へ保存することを確認。モデル名の対応表は関数単位で確認(高価な gemini-3.1-flash-image は課金回避のため実生成せず)。

注意

  • 半自動マンガの画像は cc_nanobanana に保存される。Workflow Studio v0.8.6 で、メタデータが埋め込まれた画像は Prompt タブが表示されるようにした。
  • パートナーAPIノードの認証は、これまでどおり Comic Creator 側の .env の COMFY_API_KEY(サーバー側から付与)。

2026-10-14(Nanobanana: ComfyUIネイティブノード経由の生成を併用可能に、v1.54.0)

ComfyUI v0.40.0 の確認の流れで、Nanobanana のメタデータ・履歴の扱いを見直した。現行の Gemini API 直叩き経路は、返却画像をバイト列のまま保存するため PNG に生成条件が入らず、history.jsonl への別記録が必要だった。一方 ComfyUI 本体のパートナーAPIノード(Nano Banana 2.1)は SaveImage が prompt / workflow を PNG に埋め込むため、履歴ファイルが不要になる。そこで 置き換えではなく併用 として、ネイティブ経路を追加した。

UI(Nanobanana タブ)

  • 基本設定に「生成方式」を追加(Gemini API(自分のAPIキー) / ComfyUIネイティブノード(Comfyクレジット))。選択は localStorage(ccc_nanobanana_engine)に保存。
  • ネイティブ選択時はモデル欄が Gemini Nano Banana 2.1 / Nano Banana 2 (Gemini 3.1 Flash Image) / Nano Banana 2 Lite に切り替わり、履歴の自動記録・「履歴に記録」ボタンは非表示。接続状態表示は COMFY_API_KEY の有無を見る。
  • 結果は ComfyUI の /view から表示し、Eagle 自動登録は絶対URLで行う(相対パスはプラグイン内ファイル扱いになるため)。

バックエンド(ccc.py)

  • GET /api/ccc/nanobanana/native/status(キーの有無のみ)、POST /api/ccc/nanobanana/native/generate を追加。API形式のワークフロー(GeminiNanoBanana2V2 + SaveImage、参考画像は LoadImage)を組み立て、ComfyUI 自身の /prompt へ投入して /history で完了を待つ。
  • 認証は .env の COMFY_API_KEY(platform.comfy.org で発行)を extra_data.api_key_comfy_org としてサーバー側から渡す(ブラウザへは出さない)。参考画像は temp 領域へアップロードし、name [temp] で参照する。
  • キー名: DynamicCombo の子入力は model.aspect_ratio / model.resolution / model.thinking_level、参考画像(Autogrow)は model.images.image_<n>。本体の展開ロジックで確定(未知キーは無視されるため、検証が通っても正しいとは限らない点に注意)。
  • 出力先は output/cc_nanobanana_native/NB_*.png。cc_nanobanana とは別フォルダにしたのは、Workflow Studio 側が cc_nanobanana を「メタデータ無し+history.jsonl参照」の特別扱いにしているため。
  • seed は Gemini へは送られず ComfyUI のキャッシュキーとしてだけ働く(実コードで確認)。同じ入力の再実行はキャッシュが返るため、枚数ごとに seed をずらす。ネガティブプロンプトの専用入力は無いため、従来どおり NEGATIVE_PROMPT: としてプロンプト末尾へ追記する。
  • Lite は 1K のみ(2K/4K 指定は nanobanana_native_resolution_unsupported)。エラーコード(nanobanana_native_key_missing / _unauthorized / _failed / _invalid_model / _resolution_unsupported)は i18n(ja/en/zh)に追加。

実機確認(ComfyUI_5 / ポート8189、v0.40.0)

  • ダミーキーで参考画像アップロード → /prompt 投入 → /history 判定 → nanobanana_native_unauthorized への変換まで確認(課金なし)。
  • 実キーで Nano Banana 2.1(1K・2:3)を1枚生成: 18秒、848×1264。PNG に prompt / workflow チャンクが入り、モデル・アスペクト比・解像度・thinking_level・シード・プロンプトが読める。
  • Workflow Studio の Metadata 抽出(モデル名・シード・プロンプト)と Gallery 一覧・画像メタ取得で、通常の画像として扱えることを確認(cc_nanobanana_native は history.jsonl 非対応のフォルダ)。
  • 画面操作(方式切替・生成ボタン・結果表示・「コマに挿入」の有効化)は、生成リクエストだけをスタブ化して確認。

対象外

  • 半自動マンガの一括生成(Nanobanana バッチ)は従来の Gemini API 経路のまま。
  • 既存の history.jsonl と、Workflow Studio 側の cc_nanobanana フォルダの特別扱いは、過去の画像のため残す。

2026-10-13(マニュアル執筆中に見つかった改善候補20件への対応、v1.53.2)

マニュアル執筆中に洗い出した仕様の落とし穴(manual/改善候補メモ.md、v1.53.1対象の20件)をすべて対応した。方針はユーザー判断で、実装・ヘルプ修正・項目削除のいずれかを項目ごとに選んだ。

テンプレート・ページ

  • #1 初回起動時に基本テンプレート「A4縦_3コマ」「A4横_3コマ」を自動登録(06b-template-manager.jsの_tmplSeedDefaultTemplates)。ウィザードと同じSVG形式(0.01mm単位・白背景rect + panel_0 + panel_N)を生成してparseSVGForTemplateで登録。localStorageのフラグ(ccc_default_templates_seeded_v1)で一度だけ実行し、削除しても再登録しない。既存ユーザーにも次回起動時に1回だけ追加される。テンプレート名は表示言語ごと(tmpl.defaultA4Portrait/Landscape)。
  • #2 「ページ新規作成」を「クイック」に改称(page.createPage)し、コマ線幅のprompt()を廃止。テンプレートの線幅のまま1ページ作成してレイアウトタブへ移動する(線幅の変更はレイアウトタブの「コマ枠線幅」)。insertTemplatePageToWork()への統一はせず、1ページ即作成のメニューとして位置付けた。

出力

  • #3 _updateExportPageRange: 総ページ数が変わった(作品/グループの切替等)ときだけ範囲を「1〜総数」にリセットし、同じ総数での再描画ではユーザー入力を保持。
  • #4 onSwitchToOutputTab: 作業中の作品があれば自動でその作品に絞り込む。作業中の作品が変わったときだけ適用し、手動でフィルタ解除した場合は同じ作品の間は再適用しない。
  • #9 書き出しで参照されていなかった「フキダシのみ」を削除(HTML・排他制御・i18n)。
  • #10 PDF・EPUB選択時は「背景のみ」「zip保存」を無効(グレー)表示。

i18n・表記

  • #5 トップバーCCボタンのツールチップを英語表記(web/comfyui/ccc_menu.js)。
  • #6 初回の表示言語をnavigator.languagesから決定(ja/en/zh、該当なしは日本語)。保存済みの言語設定があればそちらを優先。
  • #16 Imageタブのトースト・prompt約69種をimage.msg.*としてi18n化(ja/en/zh)。値を含む文言は関数形式。image-tab.js内ではconst tの局所変数による隠蔽があるためwindow.tで呼ぶ。Open PSDのツールチップもi18n化。
  • #17 Imageタブのテキスト入力欄をレイアウトタブの「テキスト入力」に合わせ、Enter=改行・Ctrl/Cmd+Enter=確定・Esc=キャンセルに統一。プレースホルダーにヒントを表示。
  • #7 ヘルプの「右サイドパネル」表記のうち、ツールのサブタブ説明を「プレビュー上部のツール設定帯」に修正(レイヤーパネル・表示サイズは実際に右側なので据え置き)。#11 作品管理の「グループから削除」を「stockへ移動」に修正(テンプレートタブの同名ボタンは実在するため据え置き)。#15 フキダシ形状一覧に「キャプション(直角・尻尾なし)」を追記。いずれもja/en/zh。

機能の実装・修正

  • #12 レイアウトタブ「画像」サブタブのBiRefNetを、Imageタブと同じMask Editor Oneの/mask_editor/birefnet/*ルートで実装(16-processing-edit-tabs.js)。画面を開いたときに/mask_editor/birefnet/statusで利用可否を確認し、利用できない場合は選択肢を無効化して理由をラベルに表示する。
  • #13 画像サブタブの実行結果が元画像の位置・表示サイズ・回転(transform・preserveAspectRatio)を引き継いで挿入されるようにし、成功後に元画像を非表示にする(レイヤーパネルの👁と同じ状態)。非表示化は挿入の履歴(undo)には含まれない。
  • #14 My曲線の既定画像をstatic/img/mychain_default.pngとして同梱し、読み込み先を/ccc_static/img/に変更(assets/**/*.pngは.gitignore対象のため従来は新規環境に届かなかった)。
  • #18 Nanobananaで2枚以上生成したとき、全結果をサムネイル表示し、選んだ画像を「コマに挿入」できるようにした(showResult(index)・selectedResultIndex)。
  • #19 Generateの挿入は置き換えではなく既存画像の上への重ね追加が実際の動作のため、挙動は変えずヘルプ・コードコメントを実態に合わせた。置き換えにする場合は挿入前に元画像を削除する処理が別途必要。
  • #20 Eagle設定の「ComfyUI 生成画像を自動保存」は、どの処理からも参照されていなかったためグレーアウトし、Workflow Studioの設定タブ>Eagleで設定するよう案内文を追加。保存ボタンはこの値を書き込まない。

配布

  • #8 .comfyignoreに!docs/manual/を追加し、同梱版マニュアルを配布物に含める。Registry側が否定パターンを解釈するかは未確認。

起動ログ: [ccc] Comic Creator loaded → http://127.0.0.1:8189/cccのポートが固定だったため、comfy.cli_argsのport/listenから組み立てるように修正(0.0.0.0・::は127.0.0.1、IPv6は[...]で囲む)。

検証: Kaptureで#1・#2・#3・#4・#9・#10・#12(選択肢の有効/ラベル)・#20を実機確認。Playwrightで#6(en/zh/ja/frの4ロケール)・ヘルプ文面(17項目を巡回、3言語)・#16(「New」のダイアログ・トースト・Open PSDツールチップ、3言語)を確認。#12/#13の実行、#17のテキスト入力、#18の複数枚表示はユーザーが実機確認済み。起動ログのポート組み立ては、実際に別ポートで起動せず、該当コードのみport/listenを変えて実行して確認。

How to apply: Playwrightのコンテキスト作成時のlocale指定で、初回言語・多言語表示を保存設定なしで検証できる。ヘルプのようにタブ内で項目を切り替えて描画する画面は、全項目を巡回して実画面のテキストで照合する(ソースの文字列検索だけでは描画漏れを見落とす)。


2026-10-09(Nanobanana生成履歴をJSON Lines形式へ変更、v1.53.1)

v1.53.0でリリースした生成履歴機能(自由記述のテキストブロック形式、history.txt)について、ユーザーから「Workflow Studio側で履歴ファイルを読んで別表示する連携を確実にやる」という方針が示されたため、着手前にフォーマットをJSON Lines(1行1件のJSON)へ変更した。自由記述テキストは外部ツールからのパースが不安定になるため、プログラムからも人の手編集からも扱いやすいJSONLへ先に揃えておく判断。

  • ファイル名をhistory.txt→history.jsonlに変更(py/ccc.pyの_NANOBANANA_HISTORY_FILENAME)。旧history.txtは参照しなくなるが自動削除・移行はしていない(テスト記録2件が残ったまま、ユーザー判断で削除可)。
  • handle_nanobanana_history_append: 従来の====区切りテキストブロック生成をやめ、{timestamp, engine, model, width, height, image_size, seed, filenames, prompt, negative_prompt}の辞書をjson.dumps(..., ensure_ascii=False) + '\n'で1行追記するだけに変更。engineフィールドは今回追加(既定'gemini')で、comic-creator-workflowに記録予定の次回作業(PLAN_nanobanana_local_image_edit.md、ローカルImage Edit対応)で'local'等を入れられるようにする先回りの変更。
  • handle_nanobanana_history_get: ファイルを1行ずつjson.loadsし、entries配列として返すように変更(従来の生テキストtextも互換のため残す)。手編集等で壊れた行は例外を握りつぶさず{line, raw, parse_error}としてentriesに含め、エラーで履歴全体が読めなくなることを避けた。
  • フロント(nanobanana.js): 履歴サブタブの表示は後方互換のため変更せず、fetchNanobananaHistory()が返すentriesからformatHistoryEntry()で従来と同じ見た目のブロックテキストを組み立ててtextareaへ表示する(保存形式はJSONLだが、UI上の見た目は変えていない)。appendNanobananaHistoryEntry()自体は送信フィールドの形は変えておらず、lastGenerationMetaにengine: 'gemini'を追加しただけ。
  • ヘルプ(ja/en/zh)・README(ja/en/zh)のhistory.txt表記をすべてhistory.jsonlに更新し、JSON Lines形式である旨を明記した。

検証: python -m py_compile・node --check・vm.SourceTextModuleで構文確認のみ実施。ComfyUI再起動後の実機確認(自動記録・手動記録・履歴表示、特にformatHistoryEntryが旧形式と同じ見た目で表示されること)は未実施。

How to apply: 外部ツール(Workflow Studio等)からの連携を予定しているログ・記録系ファイルは、自由記述テキストではなく最初からJSON Lines等の構造化フォーマットで設計する。UI側の見た目を変えたくない場合は、保存はJSONL・表示はクライアント側で整形、という形で両立できる(今回のformatHistoryEntry()参照)。


2026-10-09(Nanobananaタブに生成履歴(テキストファイル)機能を追加、v1.53.0)

ユーザーから「Nanobananaで生成した画像にプロンプトを埋め込み、Workflow StudioのGalleryのプロンプト/Metadataタブで見たい」という依頼を受け、まずWFS側(ComfyUI-Workflow-Studioリポジトリ)のGallery実装を調査した。

調査結果(コード変更前の事前調査): WFSの「プロンプト」「Metadata」タブ(static/js/gallery-tab.jsのrenderPromptTab・static/js/metadata-tab.js)は、PNGのtEXtチャンク(キーprompt、ComfyUI API形式のワークフローJSON)からノードを解析する仕組みで、PNG専用(py/services/gallery_service.pyの_read_png_metadata/_read_jpeg_metadataを確認。JPEGは単純なCOMコメントマーカーしか読まず、構造化されたワークフロー解析には対応していない)。Nanobanana(Gemini API)の生成結果は多くの場合JPEGで返るため、この仕組みに正攻法で乗せるには常時PNG保存への変更か、WFS側コードの拡張が必要と判明。この調査結果と、PNG保存+ダミーワークフローノード(WFS_PromptText)埋め込み案を含む3案をAskUserQuestionで提示しようとしたところ、ユーザーからツール自体を却下され、「WFSのGalleryへの埋め込みではなく、Nanobananaタブ自身の機能として生成履歴をテキストファイルで残したい」という明確な代替要望が示された。

実装(ユーザー指定仕様): 画像ファイルへの埋め込みではなく、1つのテキストファイルに「ファイル名・日時・プロンプト・設定」をまとめて記録する方式にした。編集・削除はアプリ側にUIを設けず、ユーザーがテキストファイルを直接編集する前提(この方がWFS側の実装に依存せず、PNG/JPEGどちらでも同じように使える)。

  • 保存先: output/cc_nanobanana/history.txt(画像の保存先と同じフォルダ)に、生成1回=1ブロックで追記(py/ccc.pyのhandle_nanobanana_history_append)。モデル・解像度・出力解像度・シード・ファイル名(バッチ全体をまとめて1ブロック)・Positive/Negativeプロンプトを記録。GET /api/ccc/nanobanana/historyで全文取得、POST /api/ccc/nanobanana/history/appendで追記(ルートはdispatchテーブル_DISPATCH_GET/_DISPATCH_POSTと明示add_get/add_postの両方に登録。既存エンドポイントが両方に重複登録されている既存パターンに合わせた)。各フィールドは_clip_history_field()で長さ上限(prompt/negativeは4000文字、ファイル名は300文字×最大50件)を設け、異常に大きいリクエストでファイルが際限なく肥大化しないようにした。
  • UI(templates/index.html・static/js/nanobanana.js): Nanobananaタブの基本設定に「生成時に自動で履歴に記録」チェックボックス(#nanobanana-history-auto、localStorageのccc_nanobanana_history_autoに保存、ブラウザ間では共有されない)を追加。サブタブに「履歴」を新設し、history.txtの全文を読み取り専用の<textarea>に表示+「更新」ボタン(切替時に自動読み込みも行う)。結果プレビュー欄に「履歴に記録」ボタン(直近の生成後に有効化)を追加し、自動記録チェックがOFFでも任意の生成結果だけを選んで記録できるようにした(チェックON時も含め、押せば常に新しいブロックが追記される。二重記録の防止や上書きは行わない簡素な設計)。
  • 半自動マンガの「画像を一括生成(Nanobanana)」モーダルやAutoタブのGemini設定には今回は手を入れていない(ユーザー依頼がNanobananaタブ自身の機能に限定されていたため)。

検証: python -m py_compile py/ccc.py・node --check・vm.SourceTextModuleで3ファイルの構文を確認。Pythonの変更はComfyUI再起動が必要なため、まず再起動前にKaptureでUI(チェックボックス・履歴サブタブ・記録ボタンの表示、バックエンド未反映時に404を握りつぶしてエラーメッセージ表示に倒れること)を確認。ユーザー再起動後、curlでGET/POST /api/ccc/nanobanana/historyの読み書きをダミーデータで確認(確認後history.txtを空に戻した)。続けて実機(Kapture、実際にGemini APIで1枚課金生成)で、(1)自動記録チェックON→生成→履歴タブに新しいブロックが自動追加される、(2)生成設定タブへ戻って「履歴に記録」ボタンを手動クリック→履歴タブに同じ生成のブロックがもう1件追加される、の両方を確認。コンソールエラーなし。

How to apply: 画像生成系の新しい外部API(Nanobanana以外も含む)にメタデータ記録機能を足す依頼が来たら、まず対象ツール(WFSのGallery等)側の既存仕組みがPNG専用か等の制約を確認してから実装方針をユーザーに確認する。今回のように「画像への埋め込み」から「独立したログファイル」へ要望が変わるケースがあるため、調査結果は先に共有し、複数案がある場合はAskUserQuestionで確認してから実装に入ること。


2026-10-09(v1.52.0のNanobanana実機課金テスト、Auto意思決定モデルclef-flashの動作確認)

ユーザー依頼で2件確認した。コード変更は無し(検証のみ)。

Nanobanana実課金テスト(gemini-nano-banana-2.1): 実機(Kapture、ComfyUI_5 8189)のNanobananaタブで、モデルをgemini-nano-banana-2.1にしてT2I生成(1K既定、プロンプト「宇宙飛行士の猫」)→成功、生成結果プレビューに意図通りの画像が表示された。I2I+出力解像度4Kの組み合わせは、ファイル選択ダイアログをKaptureが操作できない(comic-creator-workflow既知の制約)ため、/api/ccc/nanobanana/generateへ直接curlでPOST(images配列1枚+image_size:"4K")して検証し、返却画像をPillowで実寸確認した結果4096x4096(4K、指定どおり1:1)のJPEGが得られ、入力画像(赤い円)の指示(「バナナのキャラクターに変換」)が反映された画像が生成された。I2I配列・image_sizeパラメータが新モデルで問題なく機能することを確認。テスト用の一時ファイルは検証後に削除済み。

Auto意思決定モデル clef-flash の動作確認(Ollamaアップデートによる改善): comic-creator-workflowに記録済みの既知バグ(2026-10-04時点、clef-flash(9b)はOllama 0.35.1で/v1/systemoneが常に失敗、CUDAでnon-finite logit・CPUでcannot open model、ollama/ollama Issue #18769)について、ローカルのOllamaが0.40.1に更新されたとの申告を受け再検証した。

  • /api/versionで0.40.1を確認(0.35.1から更新済み)。/api/tagsでclef-flash:latestのcapabilitiesが["decision","vision"]になっている(旧バージョン時はvisionが付かずprojector_infoでの判定が必要だったが、現在は素直にvisionが立つ)。
  • curlで/v1/systemoneにnoul(赤い丸が写った自作テスト画像での画像判定含む)・choice(フキダシ種別)・score(コマ重要度)の3種類の質問形式を個別に投げ、いずれも200・妥当な確率値で応答することを確認(以前のような100%失敗は再現せず)。
  • 実機(Workflow StudioのSettingsタブ「Decision Model」でBackend=Ollama・Model=clef-flashに変更、ユーザー自身が設定)でAutoタブの「接続テスト」ボタンを実行し成功(1005ms, yes=0.972)。続けて既存のサンプル作品「これはTEST」の脚本に対し「意思決定モデルで推定」を2回実行し、フキダシの形・コマの重要度ともに意味のある推定結果(確率に基づく変更/一致/据え置きの内訳)を得られることを確認。
  • 副次的に判明: 1回目は6コマ中2コマ、2回目は6コマ中4コマがwsarecv: An existing connection was forcibly closed by the remote host(Ollamaの内部ランナーへのhealth/embeddingエンドポイント疎通の瞬断)で失敗した。curlでclef-flashに同一のnoul質問を6回連続送信したところ6回中1回失敗、同条件でnimble:9bに対しても同様に6回中1回失敗し、この断続的な接続エラーはclef-flash固有ではなくOllama 0.40.1側の一般的な不安定挙動(モデルを問わず発生)と判明した。auto-decision.jsは元々コマ単位で失敗を分離して処理を続行する設計(comic-creator-workflowに既知の挙動として記載済み)のため、失敗したコマ以外は正常に反映される。

結論: clef-flashを実際に使った推定(テキスト・画像とも)自体は、Ollama 0.35.1→0.40.1の更新で解消したと判断してよい(Issue #18769の症状は再現せず、確率値も実用的な精度で返っている)。ただし断続的なwsarecv系の接続エラーはOllama側の別の既知事象として残っており、clef-flash固有でも今回のアップデートで新たに生じたものでもない。CC側での対応(リトライ等)は現状の設計のままで妥当と判断し、コード変更はしていない。

How to apply: 意思決定モデル関連で「失敗した」と報告を受けたら、まずwsarecv/health/embedding等のOllama内部ランナー通信エラーかどうかをメッセージで切り分ける(モデル名に関わらず起き得るOllama側の問題で、CC側のバグではない可能性が高い)。clef-flash自体が動くかどうかを疑うときは、まずcurl .../v1/systemoneを直接複数回叩いて成功率を見るのが早い。


2026-10-07(Nanobananaタブ等: gemini-nano-banana-2.1 対応、I2I上限14枚・4K出力に拡張、v1.52.0)

ユーザー依頼で、Google公式ドキュメント(ai.google.dev/gemini-api/docs/models/gemini-nano-banana-2.1)を確認した上でNanobanana(Gemini画像生成API連携)に新モデルgemini-nano-banana-2.1を追加した。同ドキュメントによれば、本モデルは最大14枚の参照画像による多画像融合(従来のNano Banana 2の後継)と1K/2K/4K出力解像度に対応する。py/ccc.pyのhandle_nanobanana_generateはモデル名を正規表現検証(_GEMINI_MODEL_RE)するだけで、gemini-始まりのモデルは汎用的にgenerateContentエンドポイント+generationConfig.imageConfig(aspectRatio・任意のimageSize)へ渡す実装だったため、バックエンド改修は不要だった。

モデル追加(3箇所、<option value="gemini-nano-banana-2.1">を追記): Nanobananaタブ本体の#nanobanana-model(templates/index.html)、半自動マンガ「画像を一括生成(Nanobanana)」モーダルの#anb-model(static/js/main/26-auto-comic-bridge.js)、Autoタブ設定「画像生成」欄の#auto-img-gemini-model(templates/index.html)の3セレクト。いずれも既定選択は変更していない。

I2I参照画像の上限を12枚→14枚に拡張: nanobanana.jsのNanobananaManager.MAX_I2Iを14に変更(nb-i2i-countの初期表示・renderI2IGrid()の空スロット描画は元々MAX_I2Iを参照する実装だったため追従)。アラート文言(nb.maxImagesReached/nb.maxImagesTruncated)も引数化済みで無改修。

出力解像度: 2Kチェックボックス→1K/2K/4Kセレクトに変更: 旧実装はis2kチェックボックス1個でimage_size: '2K'固定だったが、4K対応のため3箇所とも<select>(値: ''=1K既定/2K/4K)に置き換えた。対象は上記3箇所と同じ(Nanobananaタブ#nanobanana-image-size、半自動マンガモーダル#anb-image-size、Autoタブ設定#auto-img-gemini-image-size)。Autoタブの永続設定(static/js/auto-ai-client.jsのDEFAULT_SETTINGS、localStorage)はimgGemini2k: false → imgGeminiImageSize: ''にキー名ごと変更(旧キーの値は単に無視される)。半自動マンガモーダルのモジュール変数も_autoNb2k → _autoNbImageSize、ランナー関数の引数もis2k → imageSizeにリネームした。バックエンドは元々image_sizeを検証なしでそのままimageConfig.imageSizeに渡す実装だったため、'1K'/'2K'/'4K'以外の値やモデルが対応しない解像度を選んだ場合の挙動はGoogle API側のエラー応答に委ねる(サーバー側での事前バリデーションは追加していない)。

i18n(ja/en/zh): nb.2kLabel/nb.2kHintを廃止し、nb.imageSizeLabel(出力解像度:/Output Size:/输出分辨率:)・nb.imageSize1k(1K(既定)/1K (default)/1K(默认))・nb.imageSizeHintを3言語で追加。

検証: node --checkに加え、comic-creator-workflowの教訓(node --checkは長い文字列リテラル中のクォート崩れを見逃すことがある)に従いnode --experimental-vm-modulesでのvm.SourceTextModule構文チェックも実施、変更対象6ファイルすべてOK。Kapture(ポート8189、ユーザーの通常のChromeとは別の新規タブ)で実機確認し、コンソールエラー無しで次を確認: (1) Nanobananaタブのモデルセレクトに新モデルが追加され、I2Iサブタブのグリッドが実際に14枚分描画され見出しが「(0/14)」になっている、(2) 半自動マンガの「画像を一括生成(Nanobanana)」モーダルの#anb-model/#anb-image-size、(3) Autoタブ設定「画像生成」欄の#auto-img-gemini-model/#auto-img-gemini-image-size。検証用タブは確認後に閉じた。Gemini APIへの実リクエスト(課金あり)による生成テストは未実施(comic-creator-workflowの方針どおり、実行前にユーザー確認が必要なため)。

判明した事実(副次的): 開発ディレクトリeagle_comic_creator_spa\comfyui-comic-creatorとComfyUI_5\custom_nodes\comfyui-comic-creatorは、今回触った7ファイル(templates/index.html・static/js/nanobanana.js・static/js/i18n.js・static/js/auto-ai-client.js・static/js/main/26-auto-comic-bridge.js・static/js/main/28-auto-tab.js・static/js/main/28a-auto-image.js)についてls -liでinode一致を確認し、現在はハードリンクで繋がっており手動コピー不要だった(過去の記録「普通のコピーで自動同期されない」と矛盾するため、comic-creator-workflowに状態確認要の注記が必要)。

残タスク: ヘルプ(22-help-tab.js、ja/en/zh)・README(3言語)への反映、実際のAPIキーでの生成テスト、リリース(バージョン上げ)は未着手。ユーザーから別途依頼があり次第対応する。


2026-10-05(VRAM調整: 画像生成の前にOllamaのモデルをアンロード、v1.51.0)

ComfyUI-LiveChatStream(LCS)のVRAM調整(/live_chat_stream/vram_prepare)が有効かを確かめ、CCへ移植した。CCはこれまでAutoタブの「モデルをアンロード」で選択中の1モデルを手動で外すだけだった。

LCSの確認(ComfyUI_5、Ollama 0.35.1): qwen3.5:9bをロードした状態でauto(目標6GB)→1台アンロードし空き470→7869MB。空きが足りていればアンロードしない。allで意思決定モデルtev1:0.8bも外れる(/api/generateが拒否されたときの/api/chatでの再試行が効く)。ComfyUIの空きVRAMはOllamaに反応しない: 9Bのロードでnvidia-smiは6530→469MBに減ったが、/system_statsのvram_freeは8462MBのままだった(LCSのv0.5.3で見つかった現象がComfyUI_5でも起きる)。そのため空きの判定はLCSと同じくnvidia-smiの実測が必要。

サーバー(py/ccc.py): POST /api/ccc/vram/prepare(mode: auto/all、target_gb: 0〜64、urls: OllamaのURL)。空きはnvidia-smi(torchのGPU UUIDで特定)+ComfyUI自身のtorch未使用キャッシュ(reserved−active)。取れなければget_free_memory。autoはOllama/api/psのsize_vramの大きい順に1台ずつアンロードし、そのつど空きが頭打ちになるまで待って(最大6秒)実測で止める(size_vramはKVキャッシュ等を含まず実消費より小さいため、見込みでは選ばない)。URLは_validate_local_urlでローカルホストに限り、既定の127.0.0.1:11434を常に加え、ポートで重複を除く(localhost/127.0.0.1/::1は同じサーバー)。通信は_local_urlopen(リダイレクトを追わない)。アンロードはkeep_alive:0で/api/generate→/api/chatの順に試し、どちらもエラーなら最大2.5秒/api/psを見て、消えていれば成功とみなす(Playwright経由の検証で、応答はエラーなのに少し遅れてアンロードされていたことが1回あった)。/api/psの失敗は[ccc] vram:でログに出し、応答のunreachableに理由を返す。

セキュリティ(コミット後のレビュー指摘に対応): 接続先はローカルホストに限っていたが、さらに4点を直した。(1) Content-Typeがapplication/json以外は415で拒否(他サイトからのtext/plain等の単純リクエストはCORSのプリフライトが無いため、モデルを外せてしまう)。(2) unreachableに例外の文字列を返さない(呼び出し側が指定したローカルホストのポートの開閉が分かるため。理由はサーバーログのみ)。(3) urlsは配列のみ・既定URLを含めて4件まで、同時実行はasyncio.Lockで1つに限り実行中は409(1件ごとに接続待ちがあり、executorのスレッドを占有できるため)。(4) target_gbのNaN/Infinityを拒否(NaNだとautoで全モデルを外していた)。

フロント: static/js/vram-prepare.js(新規)。設定はccc_vram_settings(mode: off/auto/all、targetGb、既定off・8GB、バックアップ対象に追加)。対象URLはAutoタブのローカルLLM(Ollamaのとき)と意思決定モデル(WFSと共有の設定がOllamaのとき)。ループバック以外(LAN上のOllama等)はサーバーが拒否するので送らない。14-integrations.jsのWorkflow Studioへの実行4関数(requestPanelImageFromWorkflowStudio・sendI2IRunToWorkflowStudio・sendInpaintToWorkflowStudio・sendOutpaintToWorkflowStudio)の先頭でprepareVramForGeneration()を呼ぶので、Autoタブ・オートレイアウト・スクリプトの自動作画・PixiJS FX・Imageタブのどこから生成しても効く。失敗しても生成は止めない。アンロードしたとき・目標に届かないとき・失敗したときは画面右下に通知する(目標未達の同じ警告は連続生成で繰り返さない)。設定タブに「VRAM調整(Ollama)」カード(生成前の動作・目標GB・今すぐすべてアンロード)。Autoタブの「モデルをアンロード」は、Ollamaかつローカルホストのときは同じAPIでロード中の全モデル(意思決定モデルを含む)を外す。LM Studio等とLAN上のOllamaは従来どおり選択中の1モデル。

ヘルプ・README(ja/en/zh): ヘルプの設定にVRAM調整の節を追加し、Autoタブのアンロードの説明を更新。READMEの任意設定に「Ollamaと画像生成でVRAMを取り合う場合」、APIの表に/api/ccc/vram/prepareを追加。

実機確認(ComfyUI_5): curlでルートを直接呼び、不正なmode・外部IP・認証情報つきURLの拒否、auto(空き2.6→10.1GB)、空きが足りるときのno-op、allで意思決定モデルのアンロードを確認。ブラウザはKapture(ユーザーの通常のChrome)で、ブラウザからOllamaへqwen3.5:9bとtev1:0.8bをロード→requestVramPrepareを3回(all/auto/all)繰り返し、毎回2台とも外れて空き約1.3→9.9GB(1.3〜2.1秒、失敗・接続不可0件)。prepareVramForGeneration(auto・目標6GB)でqwenが外れ「🧹 qwen3.5:9b をアンロード — 空き 2.3 → 9.6 GB」が出ること、2回目はアンロードしないことを確認(設定は確認後に元へ戻した)。設定タブのカードの表示と保存はPlaywrightで確認。Playwright経由では2回失敗があった(上記のアンロード判定と、/api/psの取得失敗で「ロード中のモデルなし」と表示)。curlとKaptureでは再現せず、検証用ブラウザ側の制約の可能性があるため、ユーザー判断でKaptureで動けば問題なしとした。Workflow Studioで実際に画像を生成しての通し確認はしていない。

How to apply:

  • ComfyUIのget_free_memoryや/system_statsのvram_freeは、Ollama等の他プロセスのVRAM使用を反映しないことがある。空きVRAMを判断材料にするときは_free_vram_mb()を使う。
  • Workflow Studio経由の生成関数を新しく足すときは、先頭でprepareVramForGeneration()を呼ぶ。
  • 意思決定モデルは/api/generateのkeep_alive:0を受け付けないことがある。Ollamaのアンロードは_ollama_unload(/api/chatで再試行し、最後に/api/psで確認)を使う。

2026-10-05(3Dポーズ: vrm-pose-editorの既定モデルをCCでも自動で読み込む、ヘルプをv0.21.0に追従、v1.51.0)

v1.50.2リリース後にヘルプを確認したところ、3Dポーズの説明が2点ずれていた。(1)「🎛 Editor」にImageタブ・自動瞬き・Blinkトラックが無い。(2)「モデル読込」の「model.glbをプロジェクト直下に置くと起動時に自動ロード」が実際には動いていなかった。CCはinitPoseEditor3DのbaseUrlに'./'を渡しており、自動読み込みは/model.glb等を探すがComfyUIでは404になる(ページが/cccのため)。v0.21.0のvrm-pose-editorはmodel/フォルダ+Default Model設定から既定モデルを選ぶ仕組み(default_model.js)になったが、ノードのpose_editor_3d.jsだけがdefaultModelProviderを渡しており、CCからは使われていなかった。ユーザー判断で、記述を消すのではなくCC側でも既定モデルを読み込むようにした。

実装(static/js/pose3d.js・main/23-pose3d-bridge.js): pose3d.jsでdefault_model.jsを他のモジュールと一緒に動的importし(失敗してもcatchしてnull=v0.21.0未満のvrm-pose-editorでも3Dポーズ自体は動く)、window.pose3dResolveDefaultModelとして公開する。window.initPoseEditor3Dは6番目の引数defaultModelProviderをそのままコアへ渡す。ブリッジ側の_pose3dDefaultModelProvider()はノードのpose_editor_3d.jsと同じ処理: .gltfはサーバーのURLのまま(外部.bin/テクスチャを相対パスで読むため)、.vrm/.glbは1回だけfetchしてBlob URLにする。VRMならバッファをstate.pose3d.modelBufferに入れてLight & Pose Editor(Pose Libraryのサムネイル生成)へ渡す。modelIsDefaultはtrueのままなので、コマ確定時にモデルは埋め込まれない(従来の既定モデルと同じ扱い)。取得中にユーザーが「モデル読込」していたら上書きしない。モデル名はツールバーのラベル(pose3d-vrm-label)に出す。Default Modelの選択UIは、Light & Pose EditorをnodeActions.loadVrmFile付きで開いたとき(ノードから開いたとき)だけ表示されるため、CCでは選べない。ノード側で選んだ設定(ComfyUIユーザーデータのvrm_pose_editor_settings.json)を共有する。

Editorボタンの目印: ツールバーの「🎛 Editor」ボタンの下辺だけを水色(#4fc3f7、2px)にした(templates/index.htmlのボタンのstyle)。ユーザーの要望。

ヘルプ・README(ja/en/zh): ヘルプの「🎛 Editor」にImageタブ(SAM 3D Body、ComfyUI 0.38以降、models/detection/)・自動瞬き・😑 Blinkトラックを追記し、「モデル読込」の自動ロードの説明をmodel/フォルダの既定モデルに書き換えた。READMEの3Dポーズの項にも既定モデルの説明を追記。

実機確認(Playwright、ComfyUI_5、回避策なし): model/にAvatarSample_F.vrmを一時的に置いた状態で、pose3dResolveDefaultModel()が/api/pose_editor/models/AvatarSample_F.vrmを返し、「コマに配置」で自動読み込みされた(ラベルがAvatarSample_F.vrm、シェイプキー15個、modelBuffer 10,712,884バイト・modelIsDefault: true)。Editorを開くとImageタブ・Blinkが表示され、Pose Libraryの「VRM 未読込のためサムネイルを生成できません」は非表示(VRMが渡っている)。model/を空に戻すと、モデル無しで3Dビューが開き、エラーは出なかった(/api/userdata/vrm_pose_editor_settings.jsonの404は設定未保存時の正常動作で、ノード側でも同じ)。確認はキャンセルで終え、ページは保存していない。

How to apply:

  • vrm-pose-editor側のdefault_model.jsの関数名・戻り値(resolveDefaultModel() → { name, url } | null)を変えると、CCの既定モデル読み込みが無言で止まる(catchしてnullにするため)。変えるときはCCのpose3d.jsも合わせる。
  • 既定モデルのmodelBufferはPose Libraryのためだけに持っている。modelBufferを見る処理を足すときは、modelIsDefaultも確認する(ユーザーが読み込んだモデルと区別する)。

2026-10-05(3Dポーズ: Light & Pose Editor表示中にプレビューが枠外へずれる不具合を修正、配信元をリポジトリへのリンクに変更、v1.50.2)

comfyui-vrm-pose-editor側の機能追加(Imageタブ=画像からSAM 3D Bodyでポーズ推定、自動瞬き等)に合わせてレイアウトタブ「3Dポーズ」との連携を確認したところ、CC側の不具合が見つかったため修正した。

不具合: 「Editor」でLight & Pose Editorを開くと、3Dビューのラッパー(state.pose3d.wrapper)はvrm-pose-editor側(light_editor.js)によってモーダル内のプレビュー枠へDOM移動され、left/topを0にリセットしてtransform: scale()で表示される(Pose Libraryも同じ方式で借りる)。ところがCC側の_pose3dSyncPositionは貸し出し中かどうかを見ずに、コマ用のleft/top/width/heightを書き込んでいた。ResizeObserver(#layout-previewとSVGを監視)がウィンドウサイズや表示倍率の変化で発火すると、モーダル内のキャンバスがコマの位置(例: left: 399px)へ飛び、プレビュー枠の外(右のプロパティ欄の下)に隠れて見えなくなった。さらに07-pages.jsのページ再描画と「再描画」ボタンは、ラッパーが#layout-previewに無ければ無条件でappendChildして戻すため、モーダルでCapture(コマに確定)→コマへの画像挿入→ページ再描画の流れで、モーダルからキャンバスを奪い返すおそれがあった。

修正(main/23-pose3d-bridge.js・main/07-pages.js): _pose3dIsBorrowed()を追加し、ラッパーが#light-pose-editor-modalまたは#pose-library-modalの中にある間を「貸し出し中」と判定する。_pose3dSyncPositionの先頭で貸し出し中なら何もしない(ResizeObserver・ページ再描画・07-pages.jsを含む全呼び出し元に効く)。「再描画」ボタンと07-pages.jsのページ再描画は、貸し出し中なら#layout-previewへ戻さない。モーダルを閉じたとき(_pose3dOnLightPoseEditorClosed)に次のフレームでコマへの位置合わせをやり直し、開いている間の表示倍率の変化にも追従させる。

配信元の変更: これまでComfyUI_5のcustom_nodes/comfyui-comic-creatorはリポジトリとは別のgitクローン(v1.49.0)で、変更を確認するたびにファイルをコピーしていた。今回、リポジトリ(eagle_comic_creator_spa/comfyui-comic-creator)への相対パスのシンボリックリンクに置き換えた(ユーザーが実施)。vrm-pose-editorも、CCのpose3d.jsが/extensions/comfyui-vrm-pose-editor/固定でimportするため、ComfyUI_5ではcustom_nodes/comfyui-vrm-pose-editorという名前のリンクにした(それまでの3dpose_light_editorという名前では404になり、3Dポーズ機能が使えなかった)。

実機確認(Kapture、ComfyUI_5、回避策なし): モデル読込→コマに配置→Editor→Imageタブで画像からSAM3D推定・適用(7.1秒)→CaptureでコマにCapture画像を確定、まで通ることを確認した。Editorを開いたまま#layout-previewの幅を変えてResizeObserverを発火させても、ラッパーはモーダル内でleft: 0のままプレビュー枠に収まる。Capture(コマに確定)後もラッパーはモーダル内に残る。閉じると#layout-previewへ戻り、途中で変わった表示倍率(80%→70%)に合わせた位置(left 482.8px→493.7px)に合わせ直された。

README(ja/en/zh): vrm-pose-editor v0.21.0のリリースに合わせ、3Dポーズの項にImageタブ(SAM 3D Bodyでのポーズ推定。ComfyUI 0.38以降、モデルはmodels/detection/に各自導入)・自動瞬き(Auto Blink)・😑 Blinkトラックを追記し、Image タブと自動瞬きにはv0.21.0以降が必要と明記した。依存関係の表に、手動インストール時はフォルダ名をcomfyui-vrm-pose-editorにする旨を追加。スクリーンショットdocs/17_3DPose_2.pngをvrm-pose-editor側のdocs/2_screenshot_pose_editor_c.png(Light/pose/Imageタブ)に差し替えた。

How to apply:

  • 3Dビューのラッパーの位置・サイズや親要素を変える処理を足すときは、先に_pose3dIsBorrowed()を確認する(モーダルに貸し出し中に触ると、vrm-pose-editor側のプレビューを壊す)。
  • vrm-pose-editor側のlight_editor.js以下はCCのページ(ComfyUI本体の画面ではない)からも動的importされる。vrm-pose-editor側で../../scripts/api.jsなどComfyUI本体専用のモジュールを静的importすると、CCで3Dポーズ機能ごと読み込めなくなる(実際に一度起きた。vrm-pose-editor側はjs/comfy_api.js経由に修正済み)。
  • ComfyUI_5のcustom_nodesは、CC・vrm-pose-editorともリポジトリへのリンクになった。ComfyUI_6など他の環境は従来どおりコピーのままなので、使う前に同期すること。

2026-10-04(ローカル中継のSSRF対策、意思決定モデルの画像入力の下地、v1.50.1)

ComfyUI-Manager へのLiveChatStream登録PR(#3340)で指摘された「リクエストで指定した接続先へサーバーが接続できてしまう」問題が、CCのローカル中継にも当てはまるため対処した(Workflow Studio側は対応済み)。あわせて、vision対応の意思決定モデル(Ollama 0.35+ のclef)に画像を渡すクライアント側の口を用意した(画像判定を使う機能は見送り)。

SSRF対策(py/ccc.py): Unsloth中継(/api/ccc/auto/unsloth-proxy)・Eagle(/api/ccc/eagle/add)・G'MIC中継は元々localhost/127.0.0.1/::1に限定していたが、(1) 3xxリダイレクトを追従していた、(2) user:pass@localhost形式の認証情報つきURLを拒否していなかった。リダイレクトを追わない_local_urlopenに3経路を統一し、認証情報つきURLは_validate_local_urlとUnsloth中継で拒否するようにした。Workflow Studioのurl_guardと同じ方針(ただしCCは元々loopbackのみで、許可リスト環境変数は設けていない)。Ollamaはブラウザから直接呼ぶためサーバー側ルートが無く、この指摘の対象外。

意思決定モデルの画像入力(decision-client.js): decide(state, questions, settings, images)に画像(base64、dataプレフィックス無し)を渡せるようにし、imageToBase64とsupportsDecisionVisionを追加した。Ollama 0.35.1のclef/clef-flashは/api/showのcapabilitiesが['decision']のみでvisionが付かないため、projector_info(画像エンコーダ)の有無でも判定する。LiveChatStreamのjudgeImageと同じ形式。

実機確認: 再起動後のComfyUI_5(8189)で、認証情報つきURL・外部IP・file://・Eagleの外部宛てが拒否され、302を返すlocalhostサーバーへは追従せずHTTP 302で止まること、localhost/[::1]宛ては通常どおり中継されることを確認。clefに赤い円の画像を渡し「赤い円があるか」0.99・「青い四角があるか」0.01。

How to apply:

  • clef-flash(9b)はOllama 0.35.1で/v1/systemoneが常に失敗する(CUDAはClef: non-finite logit、CPUはcannot open model)。ollama/ollama Issue #18769、open。画像あり・なしの両方で再現し、CC側の問題ではない。Ollamaの修正を待つ。当面はclef(27b)を使う。
  • 非visionモデルにimagesを渡すと接続が切れる。画像を渡す前にsupportsDecisionVisionで確認する。
  • ローカルサービスへの新しい中継を足すときは、_validate_local_urlで検証し_local_urlopenで接続する(urllib.request.urlopen直呼びはリダイレクトを追従する)。

2026-10-01(コマの変形・削除、テキストの縦長/横長/平行四辺形変形、バックアップの設定タブ移動、v1.50.0)

レイアウトタブにコマの変形・削除とテキストの変形を追加し、ページタブにあった一括バックアップ/復元を設定タブへ移した。ユーザーが実機(Kapture経由の確認を含む)で変形サブタブとテキスト変形の動作を確認済み。

コマの変形・削除(main/24b-panel-deform.js、新規): 「変形」サブタブでONにし、コマを選択すると輪郭・頂点・辺のハンドルをプレビューSVG上に重ねる。頂点ドラッグは自由移動、辺ドラッグは辺の向きを保った法線方向の平行移動(前後の辺の延長線との交点を新しい両端にする。平行で交点がなければ法線方向へ平行移動)。頂点削除は4頂点以上のコマだけ(三角形は不可)。コマ間の幅・外側の幅は見ず、選択コマのポリゴンだけを更新する(隣のコマと重なってもよい)。ただし自己交差する形、面積がページの0.02%未満になる形は赤表示にして確定しない。確定時はpanels[].points・背景SVG(svgContent)の枠線polygon(id=コマID)・コマ個別SVG内のclipPathのpolygonの3か所を更新する(コマ分割24a-panel-split.jsと同じ保存先)。保存は_enqueueActivePageSaveの直列化キューを通し、直前にpushHistory()するのでUndoで戻る。外周ロック(既定ON、localStorageccc_deform_outer_lock)は、サブコマを除く全コマの外接矩形をレイアウトの外周とみなし、その上にある頂点(許容差は幅の0.2%)を外周ラインから動かさない(左右の外周上はX固定・上下の外周上はY固定・角はXY固定。削除も不可)。コマ一括ロック(_isPanelLocked)中のコマも変形・削除できない。コマ削除はレイヤーパネルのコマ行の✕ボタン(04b-layer-panel-render.js)と変形サブタブのボタンからdeletePanel()を呼ぶ。サブコマ(親子を辿った子孫すべて)と背景SVGの枠線polygonも一緒に消す。selectPanelの末尾でrefreshDeformOverlay()を呼び、コマ選択が変わると変形UIを描き直す。他のサブタブへ切り替えるとモードはOFFに戻る。

テキストの変形(09d-balloon-tools.js/09e-text-tool.js): 従来は8つのハンドルすべてがフォントサイズの均等拡縮だった。角のハンドルは従来どおりにし、辺の中央のハンドル(青)を縦長/横長の伸縮に変え、Shift+ドラッグで辺に沿ったせん断(平行四辺形)にした。状態はdataset.scaleX/scaleY/shearX/shearY(せん断はtan値)に持ち、transformはtranslate(中心) rotate(角度) matrix(...) translate(-中心)で組む(無変形のときは従来どおりrotate(angle,cx,cy)だけを出力するので既存データは変わらない)。変形してもBBox中心は動かない。ハンドル・選択枠・回転ハンドルは変形後の四辺形に追従して描く(選択枠はrectからpolygonに変更)。transformを組み立てる箇所(ハンドル再描画・回転・移動・縦横書き切替)は_textBuildTransformに集約した。角ハンドルのリサイズは、BBoxの対角線ではなくハンドル座標から中心までの距離を基準にして、変形後でも飛ばないようにした。テキストサブタブに「変形リセット」ボタンを追加。

バックアップの移動と対象の追加: ページタブ「作品管理」のツールバーにあった「バックアップ」「復元」を、設定タブの「バックアップ・復元」カードへ移した(ボタンのIDは同じなので11a-work-manager.jsの処理は変更なし)。あわせて_BACKUP_LS_KEYSを見直し、これまで抜けていたccc_t2i_settings・ccc_inpaint_settings・ccc_outpaint_settings・cccPixiFxMultiPresets・Autoのccc_auto_ai_settings/ccc_auto_works/ccc_auto_current/ccc_auto_preview_show_action・ccc_split_gap_width・ccc_deform_outer_lock・cccVideoMuted/cccVideoVolumeを追加した。IndexedDBのストア(pages/templates/trash/settings)は変わっていない。WFSが書くwfm_decision_settingsはCC側では読むだけなので対象外のまま。

ヘルプ・README: ヘルプタブ(22-help-tab.js、ja/en/zh)に「コマ変形・コマ削除」を追加し、テキストのハンドル説明・ツールペイン・レイヤーパネルのコマ行の✕を更新、「バックアップと復元」をページ—作品管理から設定へ移した。README(ja/en/zh)はバックアップの場所と対象、コマ変形・削除、テキスト変形を追記し、英語・中国語版にはなかったコマ分割の項目も追加した。

実機確認(Kapture、ComfyUI_5): 配信元はStability Matrix配下のComfyUI_5のカスタムノードコピーで、リポジトリとは別物のため、変更ファイルをコピーして確認した(コピー前にdiff --strip-trailing-crで今回の変更以外の差分がないことを確認)。辺の平行移動(外周ロックONで左右の頂点がX固定のままY方向に滑る)、外周ロック中の頂点削除の拒否、ロックOFFでの頂点削除(四角形→三角形)、三角形の頂点削除の拒否、Undoでの復元を確認した。テキストは東辺・南辺の伸縮とShift+北辺のせん断で平行四辺形になること、変形後の選択枠が四辺形になることを確認した。

How to apply:

  • ComfyUI_6のコピーにはまだ同期していない。使う前に同期すること。
  • テキストのtransformを別の箇所で直接書き換えると、伸縮・せん断が消える。新たに書くときは_textBuildTransformを使う。
  • バックアップ対象のlocalStorageキーを増やしたときは、_BACKUP_LS_KEYSにも足す(ComfyUIとオリジンを共有しているため、全キーのダンプはしない方針)。
  • 変形ツールのUIを描くSVG要素(.deform-ui)はpanel-layerのSVG直下に追加している。コマ保存(savePanelSvg)はg[data-clip-panel]だけを抜き出すため混入しないが、SVG全体を保存する処理を足すときは除去対象に加えること。

2026-09-30(フキダシの自動サイズ調整、意思決定モデルによるフキダシの形・コマの重要度の推定(Workflow Studioの設定を共有)、v1.49.0)

Workflow Studio(v0.7.6〜v0.7.7)に意思決定モデル(文章を生成せず、型付きの質問に確率つきで答えるモデル。Unsloth Decision APIのLaya、Ollama 0.35+のtev1/nimble)の設定が入ったのを受け、CCでの活用として3機能を追加した。いずれもAutoタブの設定タブ「フキダシ・意思決定モデル」でON/OFFできる。ユーザーが実機で動作を確認済み(「これまで大きいフキダシで作成されていたため、サイズ調整の手間が減らせる」)。

フキダシと文字の大きさをセリフの量に合わせる(既定ON): 従来のフキダシ自動生成(オートレイアウトの転送28b-auto-layout-tab.js、スクリプトタブの「フキダシを自動生成」26-auto-comic-bridge.js)は、コマを上から均等に分割し、幅はコマの35%固定・文字サイズはフキダシの高さだけで決めていた。セリフの長さを見ないため、短いセリフでも大きなフキダシになり、長いセリフははみ出していた。寸法計算をstatic/js/balloon-fit-core.js(DOM非依存)に新設し、セリフの文字数から必要なフキダシの大きさを逆算して、コマに収まる最大の文字サイズ(既定サイズ×形状ごとの倍率が上限。バクダン1.25・思考0.95・ナレーション0.9)を8%刻みで探す。1行(縦書きは1列)の文字数の候補を2〜24字で試し、縦書きはやや縦長・横書きは横長の形に近いものを選ぶ。配置は「横並び(読み始め側から)」と「縦積み(左右ジグザグ)」の両方を計算し、はみ出しが少なく文字を大きく保てる方を採用する。最小の文字サイズ(既定の45%、最低12pt)でも収まらないフキダシは件数を表示する。折り返し・内接エリアの規則は09f-bubble-text.jsの_bubbleTextWrapLines/_bubbleTextAreaFor/_bubbleTextRenderTextと揃えた(逆算したフキダシに流し込んだとき同じ折り返しで収まるように。楕円系は√2、バクダン・雲は0.75の安全係数)。フキダシ要素の生成と文字の流し込みはmain/09g-balloon-autofit.jsのplacePanelBalloons()に共通化し、両方の呼び出し元から使う。OFFにすると従来の計算(legacyPanelBalloons())になる。

意思決定モデルでフキダシの形・コマの重要度を推定: static/js/decision-client.js(WFSの同名ファイルから必要部分を移植)とstatic/js/auto-decision.jsを新設した。接続先・モデル・しきい値はWFSのSettingsタブ「意思決定モデル」がlocalStorageに保存するwfm_decision_settingsを読むだけで、CC側では書き換えない(ユーザー要望「設定はWSと共有(意思決定モデルのみ)」。CCとWFSは同じComfyUIのオリジンで動くためlocalStorageを共有できる)。Ollamaはブラウザから直接、Unslothはサーバー中継経由で呼ぶ。脚本の各コマについて、セリフごとのフキダシの種類(speech/thought/shout/narrator)と、コマの重要度(Low/Medium/High、同じページの他のコマの描写も文脈として渡す)を聞き、確率(probabilities。confidenceはサーバーごとに計算式が違うため使わない)がしきい値以上のものだけ脚本へ書き込む。しきい値未満は元の値(LLMが付けた値や手で直した値)のまま残す。重要度はオートレイアウトの重要度按分(IMPORTANCE_WEIGHT)にそのまま効く。推定は、ONの項目について脚本の作成直後・Chatからの「脚本に反映」の直後に自動で行い、脚本タブに追加した「意思決定モデルで推定」ボタンでも実行できる(実行前にpushUndo()するのでChatの「反映を元に戻す」で戻せる)。設定タブには現在のWFS側の設定内容と接続テストボタンを表示する。

サーバー中継(py/ccc.pyの/api/ccc/auto/unsloth-proxy): 許可パスに/v1/systemoneを追加。あわせてWFSのunsloth_routes.pyと同じく、UNSLOTH_API_KEYが未設定でもヘッダー無しで中継するようにし(Unsloth DesktopのKeyless API access →「Chat and inference」がオンなら動く)、キー無しで401が返ったときは両方の対処法を案内する。Unslothのエラー本文の先頭300字もメッセージに含める(Decision APIがオフのときの404「The Decision API is off」や質問形式のエラーの原因をフロントから確認できるように)。

実測(2026-09-30、サンプル脚本とテスト用のセリフ10件): フキダシの形はnimble:9bが10件中9件正解で確率も高め(外したのは叫びとも取れる「掘れワンワン!」)、tev1:4bが8件、tev1:0.8bが7件で確率が低くしきい値0.8にほとんど届かない。当初はコマの描写もフキダシの判定に渡していたが、描写に引きずられて確率が下がり、効果音「ゴゴゴゴ…」がnarratorになる誤答も出たため、フキダシはセリフと話者だけで判定し、重要度とはリクエストを分けた。重要度はどのモデルも「中」に寄りやすく、しきい値0.8ではほとんど据え置きになる。OllamaはchoiceのcriteriaをオブジェクトでなくJSON配列で渡すとHTTP 400を返す(Unslothは配列も受け付ける)。またモデルの切り替え直後にPost ".../tokenize": ... wsarecvのエラーを返すことがあり(並列・逐次どちらでも発生)、そのコマだけ失敗として数えて続行する。

実装中に起きた不具合: 09g-balloon-autofit.jsのトップレベルで09f-bubble-text.jsのBUBBLE_TEXT_PT_TO_SVGを参照したところ、main/*の循環importによりTDZのReferenceErrorが出て、モジュール群の読み込みが止まった。モジュール評価時に他のmain/*の値を参照しないよう修正した。

How to apply:

  • 設定が共有されるのは、WFSとCCを同じアドレス(localhost/127.0.0.1の別、ポート番号も含む)で開いたときだけ。localStorageはオリジン単位のため、別のアドレスで開くとCCには「未設定」と表示される。
  • フキダシの折り返し・内接エリアの規則(09f-bubble-text.js)を変えたら、balloon-fit-core.jsの逆算も合わせること。PT_TO_SVG(3.528)もBUBBLE_TEXT_PT_TO_SVGと手で揃えている。
  • main/*のESモジュールでは、importした値をモジュールのトップレベルで使わない(関数内でのみ使う)。
  • 意思決定モデルは分野外では確率が高くても間違うことがあるため、しきい値未満は書き換えない方針を保つ。WFS側のdecision-client.jsの仕様が変わったら、CC側の移植版も見比べて同期する。

2026-09-27(コマ分割ツールの不具合2件を修正、作品未選択時のページ保存先をstockへ、ヘルプ多言語対応・README整理、v1.48.0)

前回(2026-09-26)追加したレイアウトタブのコマ分割ツールについて、ユーザーから「コマが1つのみのテンプレートをページに挿入しコマを選択してONで分割線を引いたが何も起きない」という報告を受けて調査したところ、独立した不具合2件が見つかったため両方を修正した。あわせて、検証の過程で見つかった別の不具合(作品を開いていない状態でのページ保存が作品一覧に見えなくなる)も修正し、ヘルプ・READMEへ反映した。

不具合1: コンテンツが空のコマを分割できない: _splitCommitCut(24a-panel-split.js)は分割対象コマのg[data-clip-panel]要素とそのclipPath内のpolygonをDOM上から探し、見つからなければ何もせず処理を中断していた。一方buildMergedSvg(07-pages.js)はpanel.panelSvgContentが空文字列のコマについてはコンテンツ用のg要素をSVGに一切生成しない。テンプレート挿入直後でそのコマに画像等を一度も追加していない状態ではpanelSvgContentが空のままのため、g[data-clip-panel]もそのclipPathもDOM上に存在せず、分割処理が黙って何もしないまま終了していた。これは「コマが1つだけ」に限らず「まだ何も入れていないコマ」全般で起きるバグだったが、単一コマテンプレートを挿入してすぐ分割を試すケースで特に踏みやすい。_splitCommitCut内でこれらの要素が見つからない場合は空の状態でその場で動的生成してから分割を続行するよう修正した。

不具合2: 「コマ間の幅」の符号が逆だった: 上記を修正して実機検証している最中に、「コマ間の幅」を80(コマ枠線幅63に対して)に指定して分割しても、2つの分割線が完全に重なって1本にしか見えない別の不具合を発見した。枠線はpoints境界を中心に太さ半分ずつ内外にはみ出して描かれるため、指定した幅をそのまま見た目の隙間にするには実座標上の間隔を「幅+枠線幅」にする必要があるが、実装は「幅-枠線幅」になっており、枠線幅の方が大きいと分割線同士が重なって隙間が全く見えなくなっていた(HTML側のtitle属性には「分割後の2つのコマの間に見える幅」と明記されており、意図と実装が逆転していた)。_splitCommitCutと、既存コマ間の隙間からの自動検出を行う_splitRefreshGapDefaultの両方で符号を修正し、自動検出→表示→分割実行のサイクルが自己無矛盾になるようにした。

不具合3: 作品を開いていない状態で保存・テンプレート挿入したページが見えなくなる: 上記2件の修正確認後、ユーザーから「レイアウトで開いていた作品を閉じた状態でテンプレートをページとして挿入し、フキダシを追加して保存したが作品一覧に見当たらない」という報告を受けて追加調査した。ページは必ず作品・stock(予約グループ)・任意グループのいずれかに属する運用(起動時マイグレーションで無所属ページをstockへ自動収容)になっているが、insertTemplatePageToWork(11a-work-manager.js)とレイアウトタブの保存ボタン(07-pages.js)はどちらも、作品が開かれていない場合に_pageMgrGroups.assignを呼ばずページをグループ未割り当てのまま残していた。この状態は次回起動時のマイグレーションが走るまでUIのどこにも表示されない「幽霊ページ」になる。両箇所とも、作品が無い場合はその場でSTOCK_GROUPへ割り当てるよう修正した。

実機検証(Kapture、ComfyUI_5): 単一コマのテンプレートを新規作成してページへ挿入し、コマ選択→分割ONでドラッグ→2コマに分割されること、リロード後も分割結果が永続化されていること、「コマ間の幅」に300を指定した分割で実際に見た目の隙間が反映されること、コマ2をさらに分割した際に自動検出値が保持されることを確認した。不具合3についても、作品を閉じた状態でテンプレート挿入→フキダシ追加→保存の一連の操作を行い、「ページ」タブ→「作品管理」→「グループ」タブのstockにフキダシ入りのページがサムネイル付きで表示されることを確認した。検証中、動いていない別環境(ComfyUI_6)と稼働中のComfyUI_5を取り違えて比較し、無関係な大量差分を実際の問題と誤認しかける場面があった。

ヘルプ・README反映: 上記3件について、ヘルプタブ(22-help-tab.js)を日本語・英語・中国語の3言語で更新した(「コマ分割」見出しに空コマ対応と「コマ間の幅」の正しい説明を追記、「ページ操作と保存」「左ペイン: 作品一覧/グループ」にstock自動収容の説明を追記)。あわせてユーザー依頼により、README(3言語)の「主な機能」配下の9見出し(ページ・作品管理/レイアウトタブ/Imageタブ/フォント管理/Nanobanana/Auto/スクリプトタブ/外部連携/その他)を<details><summary>によるタブごとの折りたたみ表示に変更した。英語・中国語版は元々見出し直後に空行がなくリスト表示に必要な区切りが欠けていたため、変換スクリプトで一律に空行を補いつつ改行コード(CRLF)を保持するようにした。

PLAN_*.mdをgit管理対象外に変更: ユーザー依頼により、.gitignoreにPLAN_*.mdを追加し、既にコミット済みだった3ファイル(PLAN_backlog.md/PLAN_polygon_pen_tool.md/PLAN_svg_export.md)をgit rm --cachedで追跡から除外した(ファイル自体はローカルに残る。今後追加されるPLAN_*.mdも自動的に対象外になる)。

リリース作業: DEVLOG上はv1.45.0〜v1.47.0の3エントリが既に記録されていたが、コミット漏れによりgit履歴がv1.44.0のまま止まっており、それらの変更(Autoタブ機能追加、レイヤーパネルのアセット登録、コマ分割ツール新設など)と本エントリの修正分がすべて未コミットの状態で積み上がっていた。ユーザーの指示により、これらを分割せず1コミットにまとめてv1.48.0としてリリースした。


2026-09-26(Autoタブお題入力のモーダル化・画像生成タブ分離・レイアウトタブへのコマ分割ツール追加、v1.47.0)

Autoタブの右ペイン改善3件と、レイアウトタブへの新機能追加1件を実施した。いずれもユーザー自身が実機で確認済み。

Autoタブ: お題・プロンプト入力のモーダル化、2ペイン化: 「ストーリー・脚本」サブタブの左ペイン(お題・プロンプト入力、ページ数、作成/サンプルボタン)を、既存の.text-dialog-overlayパターンを流用したモーダル(#auto-theme-dialog)へ移設し、中央+Chatの2ペイン構成に変更した(auto-2pane、Chat幅を380px→480pxに拡大)。作業バーに開くボタン(#auto-theme-open-btn)を新設し、ストーリー作成・サンプル読込成功時は自動でモーダルを閉じて中央ペインへ切り替える。ステータス表示はモーダル専用のauto-theme-statusと、作業バーの保存フィードバック用auto-statusに分離した(28-auto-tab.js)。

Autoタブ: 脚本コマからChatの画像生成プロンプトへの送信: 脚本タブの各コマの「+ セリフ」ボタン右隣に**「Send Chat Image」**ボタンを追加し、そのコマの背景描写・演技指示をワンクリックで画像生成プロンプトへ送信(既存入力は上書き)できるようにした(onScriptClickの新規send-chat-image分岐)。

Autoタブ: 画像生成をChat右隣の独立タブに分離: 従来Chat内の<details>折りたたみパネルだった画像生成UIを、Chat/画像生成/設定の3タブ構成に再編し、Chatの右隣の独立タブへ分離した(#auto-right-image新設、旧.auto-img-panelのdetails/summary用CSSは削除)。switchRightTabの対象に'image'を追加し、「Chatの返答を画像プロンプトへ」ボタン・上記のSend Chat Imageボタンのどちらも、この新しい画像生成タブへ自動的に切り替わるようにした。

レイアウトタブ: コマ分割ツールの新規追加: 「分割」ツールをツールバーに新設し、プレビュー上でコマ内をドラッグした任意角度の直線に沿って、そのコマ1つだけを2つに分割できるようにした(24a-panel-split.js新設)。幾何計算はテンプレート作成ウィザードの単一コマ分割と同じ_splitPolygonByLine(06a-polygon-geometry.js)を再利用しつつ、既存コンテンツ(画像・フキダシ・テキスト)を持つ実コマの分割に対応するため、分割後の内容振り分け(bounding boxが新コマ側に完全に収まる要素だけ新コマへ移動、分割線をまたぐ・旧コマ側の要素はそのまま残す)を新規実装した。旧コマはid・番号を変えずpointsだけ縮小し、新コマは旧コマの直後に挿入する(新コマ番号=旧+1、以降繰り下げ)。ロック中のコマは分割不可、サブコマは対象外。

実装当初、分割後の2コマの間に枠線が引かれない不具合が見つかった。原因はコマの見た目の枠線がコンテンツ層ではなく背景SVG(svgContent)に静的に描かれたpolygon(id=コマID)が担っていたためで、コンテンツ層のクリップ・画像等は正しく分割していたが、この背景ポリゴンだけ元の形のまま放置されていた。分割時にこのポリゴンも複製・分割するよう修正した。

あわせて「コマ間の幅」入力を追加し、分割後の2コマの間に他のコマ同士と同じ見た目の隙間を確保できるようにした。レイアウトタブは常にコマ枠線幅の半分ずつを内側へ自動で食い込ませて表示するため(renderLayoutTabのクリップ縮小)、_splitPolygonByLineへ渡す実際のポリゴン間隔は「入力値-現在のコマ枠線幅」に補正している(そのまま渡すと枠線幅の分だけ余計に広く見えてしまうため)。さらに、分割ツールをONにした時点・「分割」サブタブを開いた時点で、そのページの既存コマ同士の隙間(矩形の軸方向の隙間の中央値)を自動検出して入力欄へ反映するようにし、手入力の手間を無くした。最後に使った値はlocalStorageに保存する。

実機検証(Playwright、ComfyUI_5): Autoタブの一連の変更、およびレイアウトタブのコマ分割(境界をまたぐ吹き出しが旧コマに残るケース/完全に新コマへ収まるケースの両方、枠線の生成、コマ間の幅の自動検出と実際の見た目の一致、リロード後の永続化)を実機で確認した。開発リポジトリの変更は都度ComfyUI_5のcustom_nodes\comfyui-comic-creator側へ手動同期している(シンボリックリンクではないため)。

ヘルプ・README反映: 上記すべてについて、ヘルプタブ(22-help-tab.js)とREADME(3言語)を更新した。日本語(_HELP_DATA)・英語・中国語(_HELP_I18N)すべてに新規見出し「コマ分割」を追加し、Autoタブの該当見出しも更新した。作業中、英語版の追加文に未エスケープのアポストロフィ("once it's done")があり構文エラーを起こしていたのを発見・修正した(node --checkでは検出できず、過去にも同種の事故があったためvm.SourceTextModuleで検証した)。

追記(同日): オートレイアウトサブタブの英語・中国語訳を追加: 上記のヘルプ反映時点では見送っていた、Autoタブ「オートレイアウト」サブタブの8見出し(オートレイアウトサブタブ — 概要/ページ設定/テンプレート/詳細設定/中央ペイン: プレビューとツール/右ペイン: プロパティ/レイアウトタブへの転送/Generateモーダルとの連携)について、ユーザー依頼を受けて英語・中国語訳を追加した(以前のセッションから未反映のまま残っていた既存debt)。あわせて、Overview/概述見出しに残っていた「"Layout" will be added later」という古い説明(オートレイアウト自体はv1.45.0で実装済み)を、日本語版と同様に2つのサブタブを説明する内容へ修正し、Basic Workflow/基本流程の末尾にオートレイアウトサブタブへ進む手順を1項目追加した(日本語版はモーダル化対応時に追記済みだったが英語・中国語版は未追従だった)。node --checkに加えvm.SourceTextModuleによる深層構文検証も実施し、ComfyUI_5への同期・diff --strip-trailing-crでの差分なし確認も行った。PLAN_backlog.mdの該当debtエントリは完了として除去した。バージョン更新・リリースは未実施。


2026-09-25(Autoタブ Chat Toolの多言語対応、レイヤーパネルへの個別アイテムのアセット登録機能、アセットパネル「I」タブのレイアウトタブ対応、v1.46.0)

前回セッション(v1.45.0)に続けて同日中に、ユーザー報告・依頼を受けて3件の機能追加を実施した。いずれもユーザー自身が実機で確認済み(本セッションでのKapture等による実機検証は行っていない)。

Autoタブ「ストーリー・脚本」Chat Toolの多言語対応: ストーリー・脚本の本文生成は既にUI設定言語(日本語/English/中文)に対応していたが、Chat機能のツール(AUTO_TOOLS: 脚本化・セリフ推敲・ストーリー整理・キャラクター設定)がチャット欄へ送る依頼文と、Chatのシステムプロンプトが「簡潔な日本語で答えてください」で固定されており、UI言語をen/zhにしてもツール実行時の発言・AIの返答が日本語のままになる不具合があった。auto-story-core.jsにTOOL_PROMPT_BY_LANG(ツールごとの依頼文をja/en/zhで用意)とgetToolPrompt(toolId, lang)を新設し、AUTO_TOOLSからはpromptフィールドを削除。buildChatSystemMessage()にlang引数を追加し、既存のlangLabel(lang)(ストーリー・脚本生成で使っていたもの)を使って回答言語の指示を動的化した。呼び出し元(28-auto-tab.jsのonChatToolRun()・sendChat())でgetLang()を渡すよう変更。

レイヤーパネル: 個別アイテムのアセット登録: 「アセット登録を1つのオブジェクトで登録可能にしたい」という要望を受け、レイヤーパネル上部(↑↓ボタンの左隣)に**📦+**ボタンを新設した。従来、アセット登録はグループ行の📦ボタン(グループ化済みオブジェクトが対象)のみだったため、チェックボックスで選択した未グループ化の個別アイテム(画像・フキダシ・テキスト・描画図形)を対象に追加した。実装は05-groups-move.jsのsaveGroupAsAsset()が内包していたダイアログ処理(登録先グループ選択・ファイル名入力・保存API呼び出し)を_openSaveAssetDialog(defaultName, buildPayload, onSaved)として切り出し、新設したsaveSelectedLayersAsAsset()と共有する構成にした。ユーザーからのフィードバックを受け、実装後に3点の仕様調整を行った: ①複数チェック時は「複数アイテムを1つにまとめて登録」ではなく1件のみ受け付け、2件以上で押すとメッセージを表示するよう変更(layer.saveAssetOnlyOneItem)。②登録完了後、対象アイテムのチェックボックスを自動的に解除するようonSavedコールバックを追加。③チェックした1件が画像の場合、従来は(グループアセットと同様に)<g data-group-id>でラップしたSVGとして保存していたため、コマへ挿入時に画像1枚だけなのに余計なグループが生成される問題があった。これを避けるため、py/ccc.pyのhandle_save_group_assetにsvgと排他のimageフィールド(画像のdata URL)を追加し、指定時はSVGラップせずMIMEタイプ(png/jpeg/webp/svg+xml)に応じた拡張子でそのままバイナリ保存するようにした(他の画像アップロード系エンドポイントに合わせ_read_json_limitedによるサイズ制限も追加)。フキダシ・テキスト・描画図形は引き続きSVGグループとして保存し、挿入時に編集可能なネイティブ要素として復元されるようにしている。

アセットパネル「I」タブのレイアウトタブ対応: 「レイアウトのアセットパネルIタブでimageフォルダも表示可能にしたい(ImageタブのアセットパネルIタブと同じ)」という要望を受け、01-state.jsのswitchTab()内、「I」タブボタンの表示条件をtabId === 'image'のみからtabId === 'image' || tabId === 'layout'に拡張した。「I」タブの中身(#image-asset-tree)自体はメインタブに関わらず常に描画済みだったため、ボタンの表示切替だけで対応できた。挿入ボタン(image-asset-insert-btn、Aタブの「コマに挿入」・Iタブの「Open」はどちらも同じhandleInsertAsset()を共有)は元々「アクティブなメインタブがimageかどうか」で分岐していたため、レイアウトタブでは自動的に「選択中のコマへ画像として挿入」の経路(既存のA タブと同じ経路)に乗り、追加のロジック実装は不要だった。

ヘルプ反映: 上記3件について、ヘルプタブ(22-help-tab.js)の「はじめに」画面構成・「レイアウト」レイヤーパネル/グループ機能・「アセットパネル」概要/Imageタブ(I)→画像プロジェクトタブ(I)に改名・「Image」プロジェクトの保存と再編集の各見出しを日英中3言語とも更新した。見出し数がJA/EN/ZHで一致することをNode.jsスクリプトで機械検証済み(過去に未エスケープのアポストロフィで構文エラーを起こした事故があったためnode --checkに加えvm.SourceTextModuleでも構文検証した)。README(3言語)にも同内容を反映済み。

未対応: なし(本セッションの内容はヘルプ・README・バージョン更新・リリースまで完了)。


2026-09-25(フキダシ表示/非表示の誤保存バグ修正・オートレイアウト×テンプレート連携・レイヤーパネルAlt+クリック一括切替、v1.45.0)

前回セッション(Generate連携等)に続けて同日中に、ユーザー報告のバグ修正1件と、新機能2件を実施した。

フキダシ表示/非表示の誤保存バグ修正: 「レイアウトタブでフキダシを非表示にしても、別タブへ移動して戻ると表示に戻る(非表示のまま保存すれば維持される)」という報告を受け調査した。原因は04b-layer-panel-render.jsのmakeShapeItem(フキダシ)・makeTextItem(テキスト)のvis-btn/lock-btn/delete-btnハンドラが、保存先パネルIDをstate.selectedPanelId(キャンバス上で最後に選択していたコマ)から求めていたこと。レイヤーパネルはページ内の全コマのオブジェクトを1つの一覧にまとめて表示するため、操作対象のオブジェクトと選択中パネルが食い違うケースがあり、その場合は無関係な別コマへ誤って保存され、対象コマの変更はDBに反映されないまま(タブ復帰時の再構築で古い表示に戻る)という不具合だった。画像アイテム(makeImageItem)は既に「DOM上の実際の親コマ(closest('g[data-clip-panel]'))を優先する」修正が入っていたため、同じパターンをフキダシ・テキスト・描画図形(makeDrawShapeItem)にも展開した。Kaptureで、選択中パネルと異なるパネルのフキダシを非表示にしてタブ復帰しても維持されることを確認済み。

オートレイアウト × ページタブのテンプレート連携: 「オートレイアウトで作ったコマ割りをテンプレートとして保存したい」「テンプレート数が増えるとオートレイアウト側の狭い左ペインに一覧が収まらない」という要望・懸念を受け、テンプレートの一覧閲覧・管理をページタブ側に一本化する設計に変更した。ページタブのテンプレート一覧(06c-template-wizard.js)から各テンプレートカードの個別ボタンを廃止し、代わりに上部・「ページとして挿入」の右隣に**「オートレイアウトへ送信」ボタンを新設(選択中の1件に対して動作、常に1件のみ送信可能・送信し直すと置き換わる、06b-template-manager.jsの_tmplAutoStageでlocalStorage管理)。オートレイアウトの「テンプレート」タブ(28b-auto-layout-tab.js)は一覧表示をやめ、送信された1件だけを表示するよう書き換えた。あわせて、オートレイアウトに現在表示中のページ(脚本のこのページのコマ割り。まだフキダシ・画像は実体化されていない段階)をページタブの新規テンプレートとして保存できる「テンプレートとして保存」**ボタンを新設した(auto-layout-core.jsのquadsToPageLayout()で変換)。「選択解除」ボタンは、そのページのテンプレート指定を解除するのに加えて送信状態自体もあわせて解除するようにし(送信を取り消す専用ボタンは持たない方針)、ボタン名も「テンプレート指定を解除(自動生成に戻す)」→「選択解除」、「表示中のページをテンプレートとして保存」→「テンプレートとして保存」とそれぞれ短縮した(下に説明文があるため簡略化、ボタン見切れの改善も兼ねる)。実装前にレイヤーパネルの各オブジェクト種別(フキダシ/画像/テキスト/描画図形/グループ/マスクレイヤー)で表示/非表示の仕組みが同じか調査し、ユーザーに確認を取ってから進めた。Kaptureで、送信・解除・オートレイアウト側への反映・ページ→テンプレート保存(非対称スタイルのコマ割りが正しく反映されること)を確認済み(検証用に作成したテンプレート・ページは削除済み)。

レイヤーパネルのAlt+クリック一括表示/非表示切替: レイアウトタブのレイヤーパネルで、表示/非表示アイコンをAlt+クリックすると、クリックしたオブジェクトと同じ種類(フキダシ/画像/テキスト/描画図形のいずれか)のオブジェクトを、ページ全体(全コマ・オーバーレイ・下書きレイヤー、グループ内の子要素も含む)にわたって一括で表示/非表示切替する機能を追加した(04b-layer-panel-render.jsの_rlpBulkToggleSameType)。対象が複数のコマ/オーバーレイ/下書きにまたがる場合は、実際に変更が生じた範囲だけそれぞれ個別に保存する。グループ自体(表示/非表示ボタンが元々存在しない)とマスクレイヤー(表示/非表示の仕組みが別物)は対象外とした。テキストの収集では、フキダシに内蔵されたセリフ用<text>(バクダン等の内包テキスト)を誤って含めないよう、.balloon-shapeの子孫を除外するフィルタを入れている。Kaptureのevaluateで実際の<g class="balloon-shape">を2つの異なるコマへ注入し、Alt+クリック相当のaltKey:true付き合成クリックイベントをvis-btnへ発火させて、両方が一括で非表示になりIndexedDBの両コマそれぞれに個別保存されることを確認した(Kaptureのclickツール自体は装飾キー付きクリックに対応していないため、この検証手法を採った。検証用データは削除済み)。

オートレイアウト詳細設定の既定フォントサイズ変更: blankLayoutSettings()(28-auto-tab.js)の既定fontSizePtを28→80に変更(レイアウトタブの既定サイズに合わせる、ユーザー指定)。新規作成するAuto作品にのみ適用され、既存の保存済み作品の値は変わらない(ユーザーの現在の作品はそのままでよいとの回答済み)。

ヘルプ反映: レイアウトタブ「レイヤーパネル」・ページタブ「テンプレート」の使い方・Autoタブ「テンプレート(左ペイン)」の説明を日英中3言語とも更新した。作業中、英語版の追加文に未エスケープのアポストロフィ(Auto Layout's)があり構文エラーを起こしていたのを発見・修正した(node --checkでは検出できず、過去にも同種の事故があったためvm.SourceTextModuleで検証した)。

インストール先の同期漏れに関する訂正: 前回セッションまでのメモリに「comfyui-comic-creatorはシンボリックリンクで自動反映される」とあったが誤りと判明した(Get-ItemのLinkTypeが空でリパースポイントではないことを実機確認)。以後、変更ファイルはcustom_nodes\comfyui-comic-creator\側へ手動コピーが必要(comfyui-workflow-studioと同様)。

未対応: README(3言語)・PLAN_auto_tab.mdは本セッションで反映済み。バージョン更新・リリースは未実施。


2026-09-25(Autoタブの多言語対応バグ修正・LLM出力の頑健パース強化・レイアウトGenerate連携、v1.45.0)

前日の作成言語対応(UI設定言語に応じてストーリー・脚本の本文をLLMへ言語指示する機能)に対し、ユーザーから2件の指摘があり修正した。続けて、ストーリー・脚本の作成時にLLM出力が読み取れなかった不具合の調査・修正と、レイアウトタブのGenerateモーダルとAutoタブを連携させる機能追加も行った。

見出しの言語混在バグ: 英語UIでストーリーを作成すると、見出しが「# 【Title】」のように日本語の【】記号と英語が混在して出力されることが判明した。原因はstorySystemPrompt(lang)(auto-story-core.js)が見出し(【タイトル】【登場人物】【あらすじ】【ページ配分】)を日本語固定のまま渡し、本文だけ他言語で書くよう指示していたこと。モデルが見出し記号自体まで律儀に(あるいは中途半端に)訳そうとして崩れていたと考えられる。見出し記号自体もUI設定言語に応じて切り替えるSTORY_HEADINGS(ja: 【タイトル】等/en: [Title]等/zh: 【标题】等)を新設し、システムプロンプトに「この記号のまま使う(翻訳しない)」旨を明記した。ストーリー本文は他の場所でパースされていないため、見出し記号を変えても実害はない。

サンプルが日本語固定だった: 「サンプル」ボタン(LLM未接続でも流れを試せる機能)で読み込まれるお題・ストーリー・脚本(SAMPLE_THEME/SAMPLE_STORY/SAMPLE_SCRIPT)が日本語固定だったため、UI設定言語を切り替えても常に日本語の内容が読み込まれていた。同じ内容(柴犬の宇宙飛行士ポチが月面で宇宙骨を見つける話)を英語・中国語にも翻訳し、SAMPLE_THEME_BY_LANG/SAMPLE_STORY_BY_LANG/SAMPLE_SCRIPT_BY_LANGとして言語別に保持。getSampleTheme(lang)/getSampleStory(lang)/cloneSampleScript(lang)にAPIを変更し、呼び出し元(28-auto-tab.jsのonSample())でgetLang()の値を渡すようにした。

実機検証(Kapture、多言語対応バグ修正分): UI設定言語を英語→中国語→日本語の順に切り替えて「サンプル」ボタンを押し、それぞれお題・見出し・本文がすべて対応言語で一貫して表示されること(英語UIでは[Title]等、中国語UIでは【标题】等)を確認した。日本語(既定)でのサンプル・システムプロンプトの出力が変更前と完全一致することはNode.jsで検証済み。

脚本パースの頑健化(LLM出力の閉じ括弧不足を自動修復): ユーザーが「ストーリーから脚本を作成」時に「LLMの出力から脚本を読み取れませんでした」というエラーに遭遇し、提示された実際の生出力を調査した。原因はLLMの出力がpage2のパネル配列の途中で打ち切られており(pages配列とルートオブジェクトの閉じ括弧]}が2つ欠落。おそらくMax tokens到達)、JSONとして不完全だったこと。末尾に]}を1つ補うだけで完全な有効JSONに復元できることを確認した。さらに、既存の「行ベース復元」フォールバックは改行区切りを前提にしていたため、改行のない1行JSON出力(今回のケース)では機能せず、結果的に読み取り失敗になっていたことも判明。repairTruncatedJson()(auto-story-core.js)を新設し、文字列リテラルの中身を無視しつつ開き括弧をスタックで数え、不足分の閉じ括弧(と閉じられていない文字列があればその閉じクォート)を末尾に補ってから再度JSON.parseを試みるステップを、通常のJSON.parse失敗後・行ベース復元の前にparseScriptResponse()へ追加した(新しいmethod: 'json-repaired')。ユーザー提示の実際の壊れたJSONで全2ページ分すべてが正しく復元されることをNode.jsで検証済み(既存の正常系・行ベース復元・完全に読み取れないケースへの影響もないことを確認)。

レイアウトタブ「Generate」モーダルとAutoの連携(Autoから取得): レイアウトタブのGenerateモーダル(openLayoutI2IModal()、15-pixifx-bridge.js)に「Autoから取得」機能を追加した。現在Autoタブで作業中の作品の脚本ページをセレクトで選び、ボタンを押すと、その作品のストーリー全文を「全体」タブへ、選んだ脚本ページの各コマの背景描写・演技指示(下描きガイド)を対応する「コマN」タブへ、それぞれPositiveプロンプトとして反映する(既存の入力は上書き)。コマの対応付けは配列の順序のみで行い、レイアウトタブへの転送機能(詳細編集)とは完全に独立させた(転送済みページと元のAuto脚本ページの対応関係は現状正式には保存されておらず、page_auto_<timestamp>_<index>というIDの命名規則に手がかりがある程度のため、それには依存しない設計にした)。これによりI2Iバッチ生成にもAutoで作ったストーリー・脚本のデータを活用できるようになった。

実機検証(Kapture、脚本パース・Generate連携分): レイアウトタブのGenerateモーダルで「Autoから取得」を押し、「全体」タブへストーリー全文が、「コマ1」タブへ対応するactionが正しく反映されることを確認した。

未対応: README(3言語)・ヘルプは本件すべて反映済み。バージョン更新・リリースはv1.45.0として実施済み(訂正: 本行は当時「未実施」と記載していたが、後日確認したところ次セッションのv1.45.0リリースに含まれていた)。


2026-09-24(オートレイアウトサブタブの改善8件・ヘルプ反映、v1.45.0)

前日までに実装した第2弾「オートレイアウト」サブタブを、ユーザーが実際に使う中で挙がった改善要望に対応した。詳細はPLAN_auto_tab.md§12-8。ブランチfeature/auto-tabのまま。

フォントタブ連携(既定フォントに設定ボタン): フォントタブ右パネルの「選択テキストに適用」ボタンの下に「オートレイアウト」欄を新設し、「既定フォントに設定」ボタンでオートレイアウトの詳細設定タブと同じwork.layout.fontFamilyを直接設定できるようにした(確認ダイアログなしで即座に自動保存)。28-auto-tab.jsのgetAutoWork()を、auto.workが未初期化(Autoタブを一度も開いていない)状態でも呼べるようnullセーフ化した。フォント名への拡張子表示も要望に上がったが、実現にはブラウザのLocal Font Access APIでバイナリを読み込みヘッダー判定する追加処理が必要と分かったため、ユーザー判断で見送った。

コマ選択プロパティのUI調整: 右ペインのフキダシ「変更」「追加」ボタンを2行目へ移動(セリフ選択セレクトを1行目に単独化)。画像プロンプト入力欄を既定7行表示・resize:verticalでドラッグによるさらなる拡張に対応させた。

転送時のz-orderバグ修正: レイアウトタブへの転送処理(insertBalloonsAndImages())が「フキダシ作成→画像挿入」の順で処理していたため、insertImage()が常にコマのg要素内の最後(=最前面)に<image>を追加する仕様と噛み合わず、画像がフキダシを覆い隠すことがあった。処理順序を「画像挿入→フキダシ作成」に入れ替え、画像挿入後(DOM再構築後)に最新のパネルレイヤーSVGを取得し直してからフキダシを追加するよう修正した。フキダシは常に画像より前面になる(フキダシ同士の重なりは従来どおり許容)。

コマ間余白と外側余白の独立化(2段階で修正): auto-layout-core.jsのquadToPagePoints()(コマ間余白の実現方法)を2段階で修正した。第1段階は、従来「全頂点を四角形の重心方向へ一律にinset」していたためページ外周(=外側余白の境界)に接する頂点も内側へ縮み、外側余白とコマ間余白の半分が合算されて見えていた問題を、頂点の座標成分が外周と一致する場合はその成分を固定するよう修正。しかしこれには副作用があり、非対称・斜め継ぎ目スタイルで共有辺を挟む両側のコマの重心位置が異なると、区切り線が斜めに歪んで見えることがユーザー実機で発覚した。第2段階として、辺単位の多角形オフセット(各辺をその内向き法線方向へgapPx/2だけ平行移動し、隣接辺同士の交点を新頂点にする)方式に書き換え、共有辺は常に元の辺と平行になるよう修正した(Node.jsで斜め共有辺でも法線方向の距離が正確にgapPxになることを数値検証済み)。

生成のランダム性: 「生成(全ページ)」ボタンを押すたびに新しい疑似乱数シード基点(alo.seedBase)を採番し、非対称・斜め継ぎ目スタイルの配置が毎回変化するようにした。シード自体は作品データには保存せず、ページ送り等での再生成は同一セッション内のシードを使い回して一貫性を保つ。

ツールボタンのUI改善: 選択/分割ツールのアクティブ状態のスタイルを、ボーダー強調のみから他の同種トグルボタン群と共通の青背景に変更した。「分割」ツール1つを「分割(縦)」「分割(横)」の2ボタンに分離し、コマ分割の軸(縦線で左右に分けるか、横線で上下に分けるか)をユーザーが明示的に選べるようにした(従来はコマの幅・高さ比較による自動判定のみ)。

作成言語のUI設定言語対応: 「作成」(ストーリー生成)・「ストーリーから脚本を作成」時、CCのUI設定言語(日本語/English/中文)に応じてLLMへ言語指示を追加するようシステムプロンプトを書き換えた。脚本のJSON構造・キー名・importance/bubbleTypeの値は言語に関わらず維持し、"action"・"character"・"text"の中身のみ設定言語で書くよう指示する。実際にその言語で書かれるかは使用モデルの能力次第(結果は保証しない)。日本語(既定)の出力は変更前と完全一致することを確認済み。

ヘルプ反映: ヘルプタブのAutoタブヘルプで「レイアウトは今後追加予定」のまま放置されていた記述を更新し、オートレイアウトサブタブ全体(ページ設定・テンプレート・詳細設定・中央ペイン・右ペイン・レイアウトタブへの転送)の説明を新規追加した。フォントタブヘルプにも「オートレイアウト連携(既定フォントに設定ボタン)」を追加した。

実機検証(Kapture): 既定フォント設定ボタンの自動保存とオートレイアウト詳細設定への反映、フキダシ変更/追加ボタンの2行目配置、画像プロンプト欄の7行表示・リサイズ、転送後のフキダシが画像より前面に表示されること、外側余白0・コマ間余白1500pxでも外周がページ端にぴったり接すること、非対称・斜め継ぎ目スタイルで区切り線が平行になること、「生成」ボタン連打で毎回配置が変わること、分割(縦)・分割(横)ツールそれぞれの軸での手動分割、ツールボタンの青背景表示、ヘルプタブの表示、をいずれも確認。

未対応: README(3言語)は当時未反映だったが、後続セッションでv1.45.0リリース前に反映済み。バージョン更新・リリースもv1.45.0として実施済み。


2026-09-22(Autoタブ「オートレイアウト」サブタブを追加、フキダシに「キャプション」種別を追加、v1.45.0)

前日実装した第1弾「ストーリー・脚本」サブタブに続き、第2弾として脚本からのコマ割り自動生成・既存レイアウトタブへの転送を実装した。計画・詳細はPLAN_auto_tab.md§12。ブランチfeature/auto-tabのまま。サブタブ名は既存の「レイアウト」タブと混同しないよう、ユーザー指定で**「オートレイアウト」**に確定した。

自動配置アルゴリズム(static/js/auto-layout-core.js、DOM非依存の新規モジュール): 参考アプリmanga_panelの2機能(重要度按分=縦1列のみ、再帰分割=順序非保存の貪欲二分割)はどちらも単独では脚本の読み順を保持できないため、読み順を保ったまま重要度で面積按分する再帰二分割に独自に統合した。5スタイル(バランス/非対称/斜め継ぎ目/縦1列/横1列)、読み順(右から左/左から右)に対応。斜め継ぎ目スタイルはquadを4頂点で表現することで非矩形のコマにも対応する。

UI(static/js/main/28b-auto-layout-tab.js、モックアップ準拠): 左ペイン(ページ設定/テンプレート/詳細設定のタブ切替)、中央ペイン(選択・分割ツール、ズーム、ページ送り付きのSVGプレビュー)、右ペイン(選択中コマのフキダシ追加・編集、画像の生成/ギャラリー選択)。「分割」ツールは選択中コマを二分割すると同時に対応する脚本コマも複製し、コマと脚本の1対1対応を常に保つ。テンプレートタブでは既存レイアウトタブの保存済みテンプレートをコマ数一致で流用できる。

ページ設定の刷新: サイズはページタブの「新規作成」ダイアログと同じプリセット方式(標準6種+カスタム)にし、カスタムプリセットの保存先(localStorage)を共有することで両方から相互に使えるようにした。外側余白(上下左右、「均等」チェックボックスで連動編集可)を新設。既定コマ枠線幅を詳細設定タブに追加(従来ハードコードだった値を設定可能に)。「生成(全ページ)」ボタン(コマ割りを再計算するため確認ダイアログあり)はページ設定タブのみに配置し、既定フォント/既定サイズ/既定コマ枠線幅(生成には使わず転送時にのみ使う値)は詳細設定タブ専用の「保存」ボタン(確認ダイアログなし・再生成なし)に分離した。ユーザーから「生成ボタンでないとフォント設定が保存されないと誤解する/生成ボタンでコマ割りが変わると誤解する」との指摘を受けての対応。

レイアウトタブへの転送: プレビューで確定したコマ形状(手動分割済みのものを含む)からページレコードを組み立て、既存の半自動マンガ作成(26-auto-comic-bridge.js)と同じ関数群(mapScriptPageToPanels/createBalloonAtPosition/applyBubbleTextToShape/insertImage)でフキダシ・画像を流し込む。転送先ページは_pageMgrGroups/_workMeta経由でページ管理タブの「作品」としても登録する(登録しないとページ自体は編集できるが作品一覧に出ず見失いやすい)。

実機で見つけて修正したバグ3件:

  1. 転送後ページの背景が透過していた(実際のテンプレートSVGにある<rect fill="#ffffff">背景矩形が生成SVGに無かった)。
  2. 手動「分割」ツールで分割したコマが、転送後はコマ数は合っているのに配置が違っていた(転送処理が脚本panels[]からlayoutPanels()を毎回再実行しており、プレビューでの手動分割結果を無視していた)。「既存quads→panels/svgContent変換のみ」と「新規生成」の関数を分離し、転送は必ずプレビューの確定quadsを直接使うよう修正。プレビュー用の確定済みジオメトリを持っている場合、転送・保存等の最終処理では必ずそれを直接使い、元データから再計算してはならないという教訓。
  3. 外側余白の入力欄が1文字ラベル(上/下/左/右)に既存の長いラベル用固定幅クラスを流用していたため狭すぎて数値が読めず、狭い欄への誤入力で余白が1011111010という異常値になり、コマ配置領域がページ外へ押し出されて何も表示されなくなった。表示幅を修正し、contentAreaFromMargins()に余白合計がページサイズの90%を超えたら比例縮小する下限側の最終防御を追加、さらに読み込み時・保存時の両方でページ幅/高さ自体を上限に余白をクランプするようにした。数値入力UIを狭くする際は、狭さゆえの誤入力でも壊れないか(境界値クランプ)を合わせて点検するという教訓。

フキダシに「キャプション」種別を追加(関連作業): オートレイアウトのナレーション用フキダシとして使うため、既存の角丸矩形(rect、角丸半径調整可・尻尾システム付き)とは別に、常に直角・尻尾なし専用の新種別「caption」を追加した。既存rectの「角丸半径0+尻尾長さ0」設定で代用すると毎回の設定し直しが手間になるため、専用タイプとして独立させた。既存のフキダシ共通システム(09b-balloon-shapes.js)にcaption分岐を追加し、パラメータパネル(尻尾関連スライダー等)は表示させない設計。色・枠線変更・テキスト内包は既存フキダシと同様に使えるが、延長フキダシ機能は対象外(尻尾を持たないため)。レイヤーパネルに専用アイコン(▬)を追加。

実機検証(Kapture): バランス/斜め継ぎ目/縦1列スタイル・右から左読み順・手動分割・フキダシ編集・画像への文字入り・転送・作品管理登録・上記3バグの修正・キャプションフキダシの挿入/色変更/テキスト内包/保存後の再描画、をいずれも確認。未検証: テンプレート対応付けタブ、画像生成/ギャラリーピッカー、非対称/横1列スタイル、左から右の読み順(LLM・画像生成の実行やスタイルの組み合わせが多く、今回は代表的な経路のみ確認)。

未対応: ヘルプ(ja/en/zh)・README(3言語)は第1弾分のみ反映済みで、第2弾(オートレイアウト・キャプションフキダシ)分は未反映(ユーザー確認後に反映する既定の運用フロー)。バージョン更新・リリースも未実施。


2026-09-21(Autoタブ「ストーリー・脚本」を追加、v1.45.0)

参考プロトタイプnow_work/manga_panel(AIで脚本を自動生成してコマ割りに反映する荒い試作)の機能を参考に、AIでマンガのストーリー・脚本を自動作成する新タブ「Auto」を追加した。以前の方針「コマ割りの自動生成は行わない」は撤回し、「自動作成 → 人が編集して仕上げる」フローを前提とした。今回は第1弾として「ストーリー・脚本」サブタブまでを実装(「レイアウト」サブタブは枠のみ、脚本からのコマ割り連携は次段階)。計画・仕様はPLAN_auto_tab.md、UIモックアップはdocs/plan/auto_tab_mockup.png。ブランチfeature/auto-tab。

構成: 3ペイン(左=お題・ページ数・作成・サンプル/中央=ストーリー・脚本・メモ/右=Chat・設定)。作成は2段階(お題→ストーリー→編集→脚本)で、ストーリーが空の間は脚本を作成できない。脚本はページ→コマ→複数セリフの構造化された表で編集する(重要度・背景描写/演技指示・話者・フキダシ種別。重要度は将来のコマサイズ自動配分用)。既存のスクリプトタブとは連携せず、作品データは別キー(ccc_auto_current/ccc_auto_works)でlocalStorageに保存する。

AIエンジン: ローカルLLM(Ollama/LM Studio/Lemonade/Unsloth)とGemini。設定項目はWorkflow Studio(WFS)のAI TOOL設定と同じで、保存先はCC側(ccc_auto_ai_settings。WFSの設定とは共有しない)。LLMクライアントはWFSのai-tab.jsから必要部分をstatic/js/auto-ai-client.jsへ移植した(CC単体で動かすため。WFS側は無変更)。既存の橋渡し_wfmReceiveLLMPromptRequestはcallLLMに設定を渡さずThinking mode・Max tokensが効かないため、移植側では設定を必ず渡す。Geminiのキー(NANOBANANA_API_KEY)はサーバー側にしか無いため、py/ccc.pyに/api/ccc/auto/gemini-text(複数ターン・システムプロンプト対応)を新設。Unslothはキー付き中継/api/ccc/auto/unsloth-proxy(宛先はループバックに限定)を新設。

脚本のパース: LLM出力は「JSON抽出→行ベース復元」の二段構え(static/js/auto-story-core.js、DOM非依存)。ページ配列/pages付きオブジェクト/pageNum付きフラット配列のどれでも受け付ける。

Chat: 現在のストーリー・脚本を文脈に含められる。返答は自動反映せず、返答の下の「ストーリーに反映」「脚本に反映」ボタンで反映し(確認あり)、「反映を元に戻す」で戻せる(最大10回)。ツール呼び出しには依存しないため、対応していないローカルモデルでも動く。Auto向けツール(脚本化・セリフ推敲・ストーリーを整える・キャラクター設定)はAUTO_TOOLSの登録式で、追加しやすい。履歴は作品に保存(最新60件)。

画像生成: Chat内でローカル(Workflow Studio経由、既存のrequestPanelImageFromWorkflowStudio)/Gemini(Nanobanana、既存のrequestNanobananaGenerate)をラジオボタンで選び、AIによる画像プロンプト作成→生成。各エンジンの設定は設定タブの「画像生成」欄。生成画像はComfyUIのoutput配下cc_auto/に保存(py/config.pyのOUTPUT_AUTO_DIR、/api/ccc/save-auto-image、静的配信/ccc_auto_output。cc_nanobananaと同構成)。WFS経由の結果はblob URLのため、保存前にdata URLへ変換する。

実機検証(Kapture): ローカルLLM(Ollama gemma4:e4b)でストーリー生成→脚本生成(JSONを正しく解析)→Chatで脚本修正→反映→取り消し、作品の保存・読込・削除、非JSON返答での反映エラー、ローカル画像生成(zimage_turbo_t2i.json、output/cc_autoへ保存・ギャラリー表示)、AIによる画像プロンプト作成、Gemini画像生成(gemini-3.1-flash-image、1024x1024、JPEGで保存)を確認。Ollama以外のローカルLLMとGeminiのテキスト生成はユーザーが確認済み。Unsloth中継も、ユーザーがUnslothのモデルで動作を確認済み(Unslothバックエンドに対してOllamaのモデルを指定するとエラーになる件は、後日Chromeの設定リセットで解消したとユーザーより報告あり、2026-09-22)。

実機で見つけた問題: ブラウザをComfyUIの127.0.0.1で開いていると、localhost:11434のOllamaへのfetchがCORSヘッダーが正しくても"Failed to fetch"でブロックされる(127.0.0.1:11434なら通る)。既定の接続先をページのホスト名に合わせ、失敗時にURLの変更を提案するメッセージを出すようにした。なお、この現象は前日からのPC側の設定変更または不具合が原因の可能性があり、ユーザー判断で様子見としていたが、Chromeの設定リセットで改善したとの報告あり(2026-09-22)。コード側の対策(上記のヒント表示)は残置し、追加対応は不要と判断。また、ComfyUIのcustom_nodes側のCCは作業リポジトリとは別コピーのため、変更ファイルの手動同期が必要だった。ernie_t2i.jsonはComfyUI側のCLIP不整合エラー(mat1 and mat2 shapes cannot be multiplied)になったが、エラーは画面に正しく表示された(Auto側の不具合ではない)。

未対応・次の予定: 「レイアウト」サブタブ(脚本からのコマ割り自動生成、重要度によるコマサイズ按分、既存レイアウトタブ/スクリプトタブとの連携)は次セッション(2026-09-22)で着手済み、WFSの翻訳・VLMツール(レイアウトタブ作業後に検討)、I2Iは引き続き未着手。バージョン更新・リリースはv1.45.0として実施済み。ヘルプ(ja/en/zh)・README(3言語)に反映済み。

ヘッダーの高さ削減(同日、ユーザー要望): アプリタイトル「ComfyUI Comic Creator」を、タブ行の左端(同一行)へ移した(static/css/style.cssのheaderをflex横並びに、h1の余白を0・16px・nowrapに、.tabsにflex-wrap、ヘッダーの上下paddingを縮小)。ヘッダーが約90pxから46px(1行)になり、全タブの作業領域の高さが増える。狭い画面ではタブが折り返す。


2026-09-19(セキュリティチェック指摘への対応、v1.44.1)

外部のセキュリティチェック(レジストリのYARAスキャンを想定した指摘)を受け、機能に影響のない範囲でバックエンドの修正を4件実施した。

未使用コード削除 + .comfyignore 追加: handle_gmic_start_server(存在しないapp_server/start_gmic_server.batをsubprocess.Popenで起動するハンドラ)は、フロントエンドのどこからも呼ばれていない到達不能コードだったため削除した(ハンドラ本体・handle_proxy_gmic内の分岐・dispatchテーブル登録・ルート登録の計4箇所)。あわせて.comfyignoreを新規追加し、DEVLOG.md・PLAN_*.md・sample_svg/・docs/・output/をレジストリパッケージング対象から除外した(pyproject.tomlのIconが参照するdocs/thumb.pngはGitHub raw URL経由で取得されるため影響なし)。

G'MICプロキシのパストラバーサル対策: handle_proxy_gmicが/api/ccc/gmic/{tail}のtail部分を未検証のままGMIC_SERVER_URL(http://127.0.0.1:8005)へ文字列連結しており、..や不正文字を含むパスがそのまま転送される open-proxy 的形状だった。セグメント単位で[A-Za-z0-9._-]+のホワイトリスト検証を追加し、./..単体セグメントや不正文字を含むリクエストは400で拒否するようにした。

保存系APIのペイロードサイズ上限: save-nanobanana-image・save-image-project・local-gmic/open_in_gui_b64(いずれもbase64画像を含むJSONをrequest.json()で無制限に読み込んでいた)に共通ヘルパー_read_json_limited()を追加し、上限20MBでContent-Length・実バイト長の両方をチェックしてから413を返すようにした。psd/import-layers(multipart)も一括field.read()を動画アップロードと同様のチャンク読み込みに変更し、読み込み中にサイズ超過を早期検出する(上限50MB)。

model名・font weightのURL組み立て検証: Gemini API呼び出しのmodelパラメータとGoogle Fonts CSS2 APIのweightパラメータが、いずれも未検証のままf-stringでURLパス/クエリへ直接埋め込まれていた(/や?、&を含む値でリクエスト先パス・クエリを操作できる形状。APIキーはヘッダー送信のため、パス操作によるキー不正流用につながりうる)。_validate_gemini_model([A-Za-z0-9._-]{1,100})と_validate_font_weight(\d{1,3})を追加し、不正な値は400で拒否する。

検証: 全項目を実機(curl/Kapture)で検証した。G'MICプロキシはcurl --path-as-isで生のパストラバーサル文字列(../../../../etc/passwd等)を送信し拒否(400)を確認、ブラウザのfetch()はURLを自動正規化してしまうため素のcurlでの検証が必須だった。保存系APIはダミーの大容量JSON/PSDファイル(21MB/51MB)で上限超過時413・正常サイズ時200/400(PSDは解析失敗の400=サイズチェック通過の証跡)を確認(テスト生成物は削除済み)。font weightはcurlで不正値の拒否(400)と正常値(400/700)でのttf取得成功を確認。model名検証はこの環境がNANOBANANA_API_KEY未設定のため実サーバー経由では検証できず(APIキーチェックが先に働く安全側の動作)、正規表現ロジックを単体テストで確認した。

副次的な修正漏れの発見: 今回の作業とは別に、v1.41.0(PixiJS FXの複数フィルター機能対応)のリリース時にヘルプ(22-help-tab.js)は3言語とも更新済みだったが、READMEの機能一覧説明文とスクリーンショット(docs/11_pixifx.png)のalt文言が単一フィルタ時代の文言のまま反映漏れになっていたため、あわせて3言語×2箇所(計6箇所)を更新した。

How to apply: comic-creator-workflow同様、py/配下のPythonバックエンド変更は実機検証前にComfyUIサーバーの再起動が必要(JS/CSSと異なりリロードだけでは反映されない)。セキュリティ目的でリクエストサイズ上限やホワイトリスト検証を追加する際は、正常系が壊れていないことをcurl等で明示的に確認すること。また、パストラバーサル等の悪意ある入力を検証する際はブラウザのfetch()ではなくraw HTTPリクエスト(curl --path-as-is)を使うこと(ブラウザ側のURL正規化により..等が検証前に解決されてしまい、意図したテストにならない)。

ヘルプ(22-help-tab.js、日本語・英語・中国語)・README(3言語、PixiJS FX説明文の反映漏れ修正込み)に反映済み。このリリースはv1.44.1としてコミット・リリース実施。


2026-09-19(Imageタブ Mask ツールに Outpaint を追加、サイズ拡張に「拡張ピクセル」方式を追加、v1.44.0)

前回(v1.43.0)実装したサイズ拡張機能をベースに、Maskツールへ生成AIによる画像拡張(Outpaint)機能を追加した。既存のInpaint連携をそのまま流用できる設計にしたため、Workflow Studio側(別リポジトリ)の変更は一切不要だった。

サイズ拡張に「拡張ピクセル」方式を追加: 従来の透明/白/黒に加え、元画像の縁のピクセルを引き伸ばして余白を埋める方式を追加した(_edgeExtendFillCanvas())。単色塗りよりも境界の継ぎ目が目立ちにくく、後述のOutpaintの下地画像作成にも同じヘルパーを再利用する。余白色の選択ボタンは4択になったため、1列表示から2×2グリッド表示に変更した。

Outpaint機能: MaskツールのサブツールバーのInpaintボタンの右隣に「Outpaint」ボタンを追加(_wfmAvailable時のみ表示、Inpaintと同条件)。押すとツールプロパティペインに専用パネルが表示され、上下左右の余白(連動指定可)・余白の塗り方(拡張ピクセル/透明/白/黒)・Positive/Negativeプロンプト・Grow Mask By・Denoiseを設定してRunを押すだけで、次の3ステップを内部でまとめて実行する(_runOutpaint()): ①現在の合成画像を指定した余白分拡張した下地画像を作成、②余白部分=白(生成対象)・元画像部分=黒(維持)のマスクを自動生成、③Inpaintと全く同じWorkflow Studio連携(sendOutpaintToWorkflowStudio() → WFS側の既存ブリッジ_wfmReceiveInpaintRequestをそのまま再利用)で送信。成功した場合のみキャンバスサイズを拡大・既存レイヤーをシフトし、結果を新規レイヤー「Outpaint Result」として追加する(失敗時はキャンバスに変更を加えない)。Inpaintとの違いは「事前にマスクレイヤーを手動で作る必要がない」点で、ユーザーからの要望(サイズ拡張とマスク作成の2段階操作を1回のRunにまとめたい)に応えた設計。

設定タブに「Outpaint設定」を追加: Inpaint設定と対称の構成(デフォルトワークフロー使用有無+ファイル名)で、独立したlocalStorageキー(ccc_outpaint_settings)・独立したデフォルトファイル名(cc_outpaint_default.json)を持つ。Outpaintは画像の外側を生成するため、Image Editモデル等の専用ワークフローを使いたいケースを想定し、Inpaint設定とは別に切り替えられるようにした。

64px単位への自動サイズ調整(実機検証で発覚した不具合の修正): 実機検証で、512×512の画像に下方向へ150px拡張してOutpaintを実行したところ、結果画像の一部が透過(チェッカーパターンが透けて見える)になる不具合が発生した。原因は、VAEEncodeForInpaint→KSampler→VAEDecodeという拡散モデルの往復処理が、画像の幅/高さがVAEのダウンサンプリング係数(8)や、UNet内部の複数段ダウンサンプリングを考慮した安全な単位(64、ComfyUI標準の推奨値)の倍数でない場合、エンコード時に数ピクセル切り詰められた状態でデコードされて返ってくるため(512+150=662pxはどちらの倍数でもない)。返ってきた結果画像が拡張後キャンバスよりわずかに小さくなり、_loadFromDataUrl()のアスペクト比保持センタリングにより上下(または左右)に透過の隙間が生じていた。修正: _outpaintAdjustedPad()を新設し、Outpaint実行時・プレビュー表示時の両方で、実際に拡張する側の辺(ユーザーが0を指定した辺は変更しない)へ必要な分だけ余白を追加し、拡張後キャンバスサイズを64の倍数に切り上げる。プロパティパネルの「新しいサイズ」表示もこの調整後の値を表示し、「(生成モデルの制約により64px単位に自動調整されます)」と注記を追加した。

検証: 実機(Kapture)でWorkflow Studio連携の実際の生成ジョブまで実行して確認した。comfyui-vrm-pose-editor等と同様、Workflow Studio側のiframe内で使われているESモジュール(comfyUIシングルトン等)へ<script type="module">を動的挿入してdynamic importすることで、UIのクリック操作だけでは辿れないGenerate UI内部の解析結果(comfyUI.currentAnalysis)を直接読み書きし、ワークフロー読込のタイミング問題(後述)や解析結果の正しさを外部から検証できた。修正前(512+150=662px、8/64いずれの倍数でもない)で実際に透過ギャップが再現し、修正後(64の倍数である704pxに自動調整)で同条件のOutpaintを再実行したところ、キャンバス全域のアルファ値が255(完全不透明)になることを確認した。

判明した別の注意点(コード修正なし): ユーザーの実機検証で「ワークフローを読み込んで即Runを押すとInpaint対応ノードが見つからないエラーになる」という現象が発生した。調査の結果、ワークフローのJSON自体(LoadImageのMASK出力がVAEEncodeForInpaintのmask入力に接続された標準的なOutpaint構成)や、Workflow Studio側の解析ロジック(comfyui-workflow.jsのanalyzeWorkflow())自体には問題がなく、同じワークフローを読み込んで解析結果を直接確認すると正しくmask_used: trueが検出された。ワークフロー読込時の/object_info取得が初回15〜20秒程度かかるため、読込完了前にRunを押すと古い(デフォルトの)ワークフローの解析結果が使われてしまうタイミング問題と判明。Comic Creator側の不具合ではないためコード修正は行っていないが、Outpaint/Inpaintのヘルプに今後同種の問い合わせがあれば「ワークフロー読込完了を待ってからRunする」旨を案内すること。

How to apply: comic-creator-workflowと同様、Workflow Studio側の内部状態(module-scopedなシングルトン等、windowに公開されていない変数)を実機でデバッグする際は、対象のiframeのcontentDocumentに<script type="module">を動的追加してdynamic importすれば、既にロード済みのモジュールインスタンス(ESモジュールはURL単位でキャッシュされるため同一インスタンスが返る)へwindow経由でアクセスできる。この手法は今後もWorkflow Studio連携機能のデバッグに再利用できる。また、生成AIモデルの往復処理を伴う新機能(Outpaint等)でキャンバス/画像サイズを扱う際は、VAEの8倍・UNetの内部ダウンサンプリングを考慮した64の倍数への丸めを常に検討すること(8の倍数では往復処理自体は通っても、UNet内部で解像度不整合が起きる可能性がある)。

ヘルプ(22-help-tab.js、日本語・英語・中国語)・README(3言語)に反映済み。このリリースはv1.44.0としてコミット・リリース実施。


2026-09-18(Imageタブ Select ツールに「サイズ拡張」を追加し、クロップUIをツールプロパティペインへ移動、v1.43.0)

Affinity Photo等の「キャンバスサイズ変更」機能を参考に、Imageタブに画像の四辺へ余白を追加してキャンバスサイズを拡張する機能を実装した(次に予定しているOutpaint機能の前段)。

UI変更: 選択ツールバーの「✂ クロップ」ボタンの右隣に「⛶ サイズ拡張」ボタンを追加。従来クロップのX/Y/幅/高さ入力・実行/キャンセルはツールバー(ie-tool-options)に直接表示していたが、今回これをツールプロパティペイン(ie-props-pane、Selectツール選択中は通常I2Iパネルを表示する場所)へ移動した(_renderCropProps())。サイズ拡張のUIも同じ場所に表示する(_renderExtendProps())。クロップ・拡張どちらのモードでもない場合は従来通りI2Iパネルを表示し、3つの表示を相互排他的に切り替える。

サイズ拡張の実装: 上/右/下/左を個別にピクセル単位で指定でき、「四辺を連動させる」チェックで1箇所の入力を四辺すべてに反映できる。余白の色は透明/白/黒から選択可能(_applyExtend())。クロップ(_applyCrop())が「キャンバスサイズを縮小し各レイヤーのx/yをクロップ原点分シフトするだけ」で実現しているのと対称的に、拡張は「キャンバスサイズを拡大し各レイヤーのx/yを追加した上・左の余白分シフトするだけ」で実現している(レイヤーはページ座標系に独立配置されているため、どちらも座標シフトのみで済む。詳細はLayerManager.composite参照)。透明以外の余白色を選んだ場合は、合成結果が透過背景のままだと書き出し画像に色が反映されないため、新キャンバス全体を塗りつぶす描画レイヤー(Layer)をLayerManager.addLayer()で作成し、配列末尾(最背面)へ移動してから追加する。

レイアウト修正: プロパティペインは幅160pxと狭く、.ie-props-rowはデフォルトでflex-direction: column(ラベルの上に入力欄を1つ表示する縦積み)。当初、拡張パネルの「四辺を連動させる(均等に追加)」チェックボックスのラベルや「新しいサイズ: ◯◯ × ◯◯ px」の表示を1行のテキストとして詰め込んだところ、パネル幅を超えて横スクロールが発生した。ラベルにはwhite-space: normalを明示して折り返し可能にし、「新しいサイズ」は<br>で2行に分割して解決。さらに、クロップ・拡張・既存のI2Iパネル(Positive/Negative Prompt・Workflow file入力)双方の数値/テキスト入力に共通する問題として、width:100%を指定した<input>/<textarea>が既定のcontent-boxのままだと、要素自身のpadding/borderが幅に加算されてパネル幅をわずかに超え、同じく横スクロールの原因になっていた。該当する全ての入力欄にbox-sizing: border-boxを明示して解決した。

検証: Kaptureで実機(window._ccImageTabのプロトタイプへ変更後のメソッドを直接代入してリロード無しに検証)を用い、四辺連動のチェック→数値同期→白選択→実行→1024×1024から1224×1224への拡張(背景にBackground Fillレイヤーが最背面に追加)→クロップUIのプロパティペイン表示→Undoでの復元、を一通り確認済み。横スクロール解消もie-props-bodyのscrollWidthとclientWidthが一致することを実機で確認。

How to apply: プロパティペイン(ie-props-pane、幅160px)に新しいUIを追加する際は、.ie-props-rowが縦積みレイアウトである前提を踏まえ、複数の短いラベル+入力欄を1行に並べようとしない(狙って並べたい場合はflex-direction:rowを明示指定した専用の行にする)。また同ペイン内の<input>/<textarea>にwidth:100%を指定する場合は必ずbox-sizing:border-boxも併記すること(このコードベースには* { box-sizing: border-box }のようなグローバルリセットが存在しないため)。

ヘルプ(22-help-tab.js、日本語・英語・中国語)・README(3言語)に反映済み。このリリースはv1.43.0としてコミット・リリース実施。


2026-09-14(レイアウトタブに動画ツールを追加 — MP4配置・再生・音量調整・フレームキャプチャ、v1.42.0)

今後の動画生成ワークフロー(ComfyUI側)の出力をマンガ制作に活用することを見据え、レイアウトタブに新規ツール「動画」を実験的に追加した。MP4動画をコマに配置し、再生・停止・音量/ミュート調整・任意フレームでの静止画キャプチャができる。

配置経路: (1) コマへのMP4ファイルのドラッグ&ドロップ、(2) Workflow StudioのGalleryタブから「Send CC」で送信。(2)は別リポジトリComfyUI-Workflow-Studio側のgallery-tab.jsにも動画判定分岐を追加し、動画選択時はwindow.parent.insertVideoFromUrl(url, filename)を呼ぶよう対応した(Workflow Studio側はv0.6.3として別途リリース済み)。

データ設計: 動画本体はIndexedDBへ複製保存せず、常にComfyUIサーバー側(output/cc_video_assets/、新設のPOST /api/ccc/video/uploadでアップロード、チャンク書き込みで300MB上限を検出)のファイルをURL参照する方式にした。SVG側は代表フレーム(先頭フレーム)のサムネイルを通常の<image>要素としてbase64 hrefで埋め込み、data-video-src/data-video-name属性に参照URLを持たせる(3Dポーズ/3Dテキストの再編集用メタデータ埋め込みと同じdata-*属性パターン)。この設計により、同じ動画を複数コマ・複数ページで使い回してもページレコード(IndexedDBのpagesストア)が肥大化しない。

UI/操作: 3Dポーズ/3Dテキストと同じ「オーバーレイ表示」パターンを踏襲し、新規ファイル27-video-bridge.jsを作成。レイヤーパネルで動画オブジェクト(🎬アイコン)を選択した状態で「動画」ツールを開くと、そのコマの位置に実DOM要素の<video>をオーバーレイ表示する(imgEl.getBoundingClientRect()を直接使うため、3Dポーズのような手動SVG座標→CTM変換が不要でシンプルに実装できた)。▶再生/■停止/シークバー/🔊ミュート/音量スライダーをツールペインに配置し、音量・ミュート設定はlocalStorageに保存され次回も引き継がれる。「このフレームで静止画作成」ボタンは現在のフレームをcanvasにdrawImageして新規画像オブジェクトとして同じコマに追加する——3Dポーズ/3Dテキストの「確定」と異なり、元の動画オブジェクトは変更せずそのまま残す(他のコマでの再利用を想定したユーザー要件)。

参考実装: Workflow StudioのVideoタブ「Capture Frame」機能(<video>をcanvasにdrawImage→toBlob/toDataURLでフレーム抽出する手法)を踏襲した。

検証: 構文チェック(node --check/python -m py_compile)を全変更ファイルに実施。実機での一連の動作(D&D配置・再生・音量/ミュート・フレームキャプチャ・Send CC往復)はユーザーによる実機確認済み。

How to apply: 動画のような大きいバイナリデータを、このアプリの既存の永続化設計(ページのSVG文字列に画像をbase64 data URLとして丸ごと埋め込みIndexedDBへ保存)にそのまま乗せようとすると肥大化・非効率になる。サイズが大きく複数箇所から参照されうるアセットは、サーバー側にファイルとして保存しURLで参照する設計の方が、既存の画像アセット管理(assets/フォルダ)とも一貫性が保てる。また、別リポジトリ(Workflow Studio)とのwindow.parent/window.opener経由のクロスウィンドウ連携APIに新しい受け口を追加する際は、呼び出し元側の実装も同時に更新しないと機能しない(Send CCボタン等)。

このリリースはv1.42.0としてコミット・リリース実施。


2026-09-14(PixiJS FX が comfyUI-particle-pixijs の複数フィルター機能に対応、v1.41.0)

comfyUI-particle-pixijs 側で openFilterLibrary() のAPIが単一 filterSettings オブジェクトから複数フィルターの filterStack 配列へ刷新されたのに伴い、Comic Creator の PixiJS FX(static/js/pixifx.js)が追従できておらず、モーダルを開いた瞬間にクラッシュしていた(filterStack 未対応のまま filterSettings を渡していたため、内部で undefined を JSON.parse して例外)。

修正: pixifx.js を新APIに合わせて全面的に追従させた。

  • 内部状態を filterSettings(単一)→ filterStack(配列)に変更。旧形式で保存されていたlocalStorageの設定(cccPixiFxSettings)からの自動移行も追加し、既存ユーザーの設定を失わないようにした。
  • applyFilter() を、複数フィルターを順序通り適用するロジックに刷新(ComfyUI本体ノード側の particle_widget.js と同一ロジック。SCENE_WIDE系〔godray等〕とレイヤー系フィルターの適用先コンテナ振り分けも踏襲)。
  • onPreview/onSave が配列を受け取る新シグネチャに対応。
  • マルチフィルタープリセット(レシピ)の保存/読込/削除を追加。ComfyUI本体ノード側は ComfyUI の userdata API を使うが、このSPAは ComfyUI フロントエンド外からも使われる設計のため localStorage(cccPixiFxMultiPresets)で実装。

結果として、PixiJS FX は単一フィルターだけでなく複数フィルターの重ね掛け・並び替え・プリセット保存が可能になった。

検証: Playwrightで実機(/ccc、ユーザーの実データが入った編集中プロジェクト)を操作し、モーダルが正常に開くこと(旧形式のCRTフィルター設定が新形式へ自動移行され正しく復元されることを確認)、マルチタブのレシピ一覧が空状態から正常表示されること、コンソールエラーが無いことを確認した。実データへの影響を避けるため、適用(レイヤーへの反映)は行わずキャンセルで検証を完了させた。

How to apply: comfyUI-particle-pixijs 側で openFilterLibrary() のAPIを変更する際は、外部SPA側の呼び出し元(pixifx.js)も同時に更新しないと連携が壊れる。逆にこのSPA側の applyFilter() の挙動を変える際も、ComfyUI本体ノード側の particle_widget.js の実装と重複しているため両者を同期させること。

このリリースはv1.41.0としてコミット・リリース実施。


2026-09-09(Imageタブ: キャンバス外オブジェクトの操作性、3Dテキスト確定時のジャギーを改善、v1.40.0)

ユーザー依頼: Imageタブで①オブジェクトがキャンバスサイズ外に出ると表示が切れて配置が困難、②3Dテキストを確定すると荒くなる、の2点を改善したい。

1. キャンバス外オブジェクトの操作性: HTML canvasの仕様上、要素の描画バッファを超えた部分は描画できずピクセル内容自体は表示できない。ユーザーとの合意で「ピクセルは見えなくてよいので、選択枠・ハンドルでの移動/回転/リサイズ操作ができれば十分」という条件に絞り、Selectツール専用の新しいcanvas要素ie-canvas-select-overlayを追加(image-tab.js���templates/index.html/static/css/image-tab.css)。キャンバス実サイズの四方にmargin = clamp(150, 500, max(w,h)*0.5)だけ拡張したサイズで確保し、選択枠・角ハンドル・回転ハンドルがキャンバス外にはみ出しても表示・操作できるようにした。既存のie-canvas-overlay(Crop/Blur/Shape/MaskVector/MaskShapeが使用)は変更せず、影響範囲をSelectツールに限定。SelectTool.setCanvas(canvas, margin)にmargin引数を追加しctx.translate(margin, margin)で吸収することで、SelectTool内部のヒットテスト・座標計算はキャンバス座標系のまま変更不要にした。副次的に、キャンバス内側の透過部分に実際のチェッカーパターンを描画する_drawTransparencyBg()を追加し、ワークスペース背景(暗いグレー系)と区別できるようにしてキャンバス境界を視覚的に明示した。

2. 3Dテキスト確定時のジャギー対策: image-tab/Text3DTool.jsの3Dプレビューは常に480×320(CSS)×devicePixelRatio固定の低解像度でレンダリングされ、commit()はそのままPNG化してレイヤーへ焼き込んでいたため、Imageタブ側でSelectToolを使って拡大配置すると粗さが目立っていた。当初「確定直前に固定3倍解像度でキャプチャ」で対応したが、ユーザーから「改善を感じる時と感じない時がある」「キャンバスが大きいとプレビューウィンドウが小さくて操作しづらい」という指摘を受け再修正。原因は、固定倍率だけではキャンバス座標系での実際の配置サイズ(ズーム状態により変動)を無視していたこと、およびプレビューのCSS表示サイズにズーム倍率をそのまま掛けていたため大きいキャンバスをズームアウトして編集しているとプレビューウィンドウ自体が縮んでいたこと。レイアウトタブ(main/25-text3d-bridge.js)と比較すると、レイアウトタブは配置先の「コマ」の実表示ピクセルサイズにプレビュー解像度をリアルタイム追従させているため常に必要十分な解像度になっており、ジャギーが出ない。Imageタブには「コマ」に相当する配置先の枠が無いため同じ方式はそのまま持ち込めず、代わりにText3DTool.jsのopen()でオーバーレイの画面上CSS表示サイズを常に480×320固定にし(ズーム倍率を掛けない)、キャンバス座標系での配置サイズ(displayW/displayH)をズーム倍率の逆数から動的計算(キャンバス実サイズの60%を上限にクランプ)するよう変更。commit()のキャプチャ解像度も固定倍率ではなく、この配置サイズを基準に「1pxあたり最低2.5px(TARGET_DENSITY)」を確保する動的計算に変更した(MAX_CAPTURE=4096で上限クランプ)。

検証: このセッションでは全ファイルの構文チェック(node --check)のみ実施し、ComfyUIサーバーを起動してのブラウザ実機検証はユーザー自身が行った(「キャンバス外配置OKです」を確認、3Dテキストのジャギー改善は「確認できるときとできないときがある」状態でユーザーが許容し現状で確定とした)。

既知の未解決点(次回申し送り): 3Dテキストレイヤーの再編集(ie-select-text3d-edit-btn)は既存レイヤーのdisplayW/displayHをText3DTool.open()に渡しておらず、再編集の都度その時点のズーム状態から新しいサイズが再計算されてレイヤーへ上書きされる(今回のズーム連動化により再編集時にサイズが変わりやすくなった可能性がある)。

How to apply: 配置先に「コマ」のような固定枠がある機能(レイアウトタブ)ではプレビュー解像度をその枠の実表示サイズに追従させるのが最も確実。固定枠が無い機能(Imageタブの任意配置)では、キャンバス座標系での実際の配置サイズを基準にキャプチャ解像度を動的計算する方式が、固定倍率よりも環境・ズーム状態によるブレが少ない。


2026-09-09(Fillツールのグラデーション操作パネル、ペンタブレットでスライド操作ができない不具合を修正、v1.40.0)

ComfyUI-Workflow-Studio側でComic CreatorのImageタブのFillTool.js(バケツ塗りつぶし、Solid/Gradient対応)を移植した際、移植先で「Fillのグラデーション設定パネルにある色ストップランプ(Ramp)・方向ダイヤル(Direction)がペンタブレットでスライド操作できない」という実機フィードバックを受けた。原因を修正したところComic Creator本体(移植元)にも全く同じ不具合が存在することが判明したため、こちらにも同じ修正を横展開した。

原因: image-tab.jsの_setupFillGradientRamp()(グラデーションランプのストップ選択・ドラッグ)、_setupFillGradientDir()(方向・強度ミニダイヤル)、および同一パターンのコードが複製されていたShapeツールの_setupShapeGradRamp()(塗りグラデーションランプ)の3箇所だけが、他のPointer Events化済みの描画系ツールと異なりmousedown/mousemove/mouseupのまま実装されていた。touch-actionが未指定でブラウザのパン/スクロールジェスチャに奪われやすく、ドラッグ中のmousemoveをdocumentに張っていたためポインタキャプチャも無く、ペンの高速な動きを取りこぼしやすい状態だった。

修正: 3箇所ともpointerdown/pointermove/pointerup/pointercancelに変更し、対象canvasにtouch-action: noneを設定、canvas.setPointerCapture(e.pointerId)でドラッグ中はポインタをキャプチャ、ドラッグ中のリスナーもdocumentではなくcanvas自身に張るよう変更した。

検証: Kaptureで実機(ComfyUI_5上のComic Creator /ccc)を操作し、Fill/Shapeそれぞれのグラデーションランプでストップ選択(クリックで青ハイライト)が正常動作しコンソールエラーが無いことを確認。ペン特有のドラッグ挙動そのものは自動化ツールでは検証できないため未確認(本件はワークフロースタジオ側でのユーザー実機フィードバックが発端)。

How to apply: キャンバスベースの新規ドラッグ/描画UIは最初からPointer Events + touch-action:none + setPointerCaptureで実装する。同一パターンのコードが複数箇所(今回はファイル間・同一ファイル内の両方)に複製されている場合、1箇所の不具合報告を機に全箇所を横展開で確認する。

このリリースはv1.40.0としてコミット・リリース実施。


2026-09-08(Image/レイアウトタブの描画にペンタブレット筆圧対応、DBレコード破損の防御・復旧、v1.39.0)

ユーザー依頼: 「Imageタブでペンタブレットで描画したい」。実装・検証を進める中で、レイアウトタブのペイント・マスクへの展開、動作確認中に発生したPCハングアップの原因調査・対策、その後発覚した既存不具合(マスクのストローク途切れ)の修正、IndexedDBレコード破損の調査・防御・復旧まで、一連の作業を行った。

1. Imageタブの筆圧対応: image-tab.js の描画イベントを mousedown/mousemove/mouseup から Pointer Events (pointerdown/pointermove/pointerup) へ移行し、event.pressure(0〜1、マウスは0.5固定)を image-tab/DrawTool.js・image-tab/MaskTool.js の onMouseDown/onMouseMove へ伝播。両ツールに _effectiveSize/_effectiveOpacity(0.3 + pressure*1.4 等の係数でマウス操作時=pressure 0.5を1.0倍に正規化)を追加し、ブラシサイズ・不透明度(DrawToolのみ)を筆圧に応じて可変化。描画キャンバスに touch-action: none を設定しペン/タッチでの誤スクロールを防止。Kaptureで合成PointerEvent(pointerType: 'pen')を発火させ、低圧力/高圧力でブラシの太さ・濃さが実際に変化することをピクセル解析で確認。

2. レイアウトタブのペイント・マスクへの展開: main/04a-mask-core.js(マスク。既にPointer Events化済みだったため筆圧受け渡しのみ追加)と main/17d-layer-draw-paint.js(ペイント。mousedown系のままだったためPointer Events化から実施)に同様の筆圧対応を追加。

3. ペイント機能によるPCハングアップの原因調査・対策: 実機のペンタブレットで動作確認中、ユーザー環境でPCがハングアップ(NETIO.SYSがクラッシュとして観測、AdGuard等セキュリティソフトの影響も疑われたが127.0.0.1は保護対象外にしていた)。原因は 17d-layer-draw-paint.js の _paintStrokeAt が pointermoveのたびに canvas.toDataURL('image/png')(PNGエンコード+Base64化+SVG <image href> 再デコード)という重い処理を実行していたこと。ペンタブレットの高頻度pointermove(マウスの数十倍のレートになりうる)でこれが繰り返されUIスレッドが占有され続け、システム全体の応答性低下→他ドライバのハングという連鎖が疑われた。対策として設計変更: ストローク中は _paintToolState.canvas(オフスクリーンの実データ)への描画とオーバーレイcanvas上へのプレビュー描画(新設 _paintRenderPreview/_paintSvgToOverlay、el の位置・回転をオーバーレイ座標へ変換して重ね描き)のみを行い、requestAnimationFrame でプレビュー描画自体もスロットリング(_paintSchedulePreview)。実際の <image href> への確定保存(重いtoDataURL)は新設 _paintBakeToImage が pointerup時にのみ 実行(ツール切り替え等でストローク中断された場合の保護も追加)。ユーザー確認により改善を確認。

4. マスクの「ストロークが短く途切れる」不具合の調査・修正(2件): ペイント改善確認後、マスクで新たに報告。

  • 1件目: 04a-mask-core.js の _maskPointerLeave が、setPointerCapture 済みにもかかわらず pointerleave のたびにストロークを強制終了させていた(本来 setPointerCapture 中は要素境界を跨いでも pointermove/up はキャプチャ元に届き続けるため、pointerleave で終了させる必要はない)。ペンを素早く動かすと一瞬の境界越えでストロークが分断されていたと推定。pointerleave では終了させず、代わりに保険として pointercancel イベントでのみ _maskPointerUp を呼ぶよう修正。
  • 2件目(1件目の修正後も再現): マスクの編集用オーバーレイcanvasに touch-action: none が設定されていなかった(ペイント側は設定済みだったが、マスク側は元々の実装に無かった)。これが無いとペンで一定距離動かした時点でブラウザがスクロール/パンジェスチャーと誤判定し、Pointer Eventsの配信が止まる。「ほぼ同じ長さで切れる」という症状と一致。_maskAttachOverlay のcanvas生成時に touch-action:none を追加し、ユーザー確認により改善を確認。

5. IndexedDBレコード破損の防御・復旧: ユーザーが Uncaught TypeError: Cannot read properties of null (reading 'name') at req.onsuccess (00-db.js:210:29) を報告(併せて「ページタブのテンプレートが1つしかない」という症状、前回のハングアップとの関連を推測)。dbGetAllPagesMeta()(main/00-db.js)が pages ストアをカーソル走査中、あるレコードの cursor.value が null(前回ハングアップ時の保存処理の中断による破損と推定)だったため例外を投げ、onsuccess ハンドラ内の例外のためPromiseがresolve/rejectされず呼び出し元が永久にpendingになっていたと判明(一覧取得がそこで止まって見える)。防御: cursor.value が falsy ならその1件をスキップして cursor.continue() する(console.warn で通知)よう修正。復旧: Kaptureで ComicCreatorDB を直接調査し、pages ストアの test_new_20260714225304_1784037192978(同一作品の他2ページは正常)が実際に value: null であることを確認、store.delete() で削除しクリーンアップ(元データ自体は失われていたため中身の復旧は不可、ユーザーも「壊れていれば復旧不要」と許容)。ついでにユーザーから「グループに"tate"/"yoko"があった記憶がある」との確認依頼を受け、localStorage(page_groups/template_groups/work_size_presets)を調査したが該当なし(記憶違いの可能性、実害報告なし)。

6. ドキュメント更新: ヘルプタブ(日英中3言語、22-help-tab.js)に上記の筆圧対応を反映。レイアウトタブの「マスク」サブタブがヘルプに詳細見出しを持っていなかった(ツールペインの参照リストにも漏れていた)ことに気づき、この機会に新設(対象・レイヤー種別・編集・赤プレビュー・一括操作+筆圧対応、3言語)。README(日英中3言語)にも同内容を反映し、同様に抜けていた「マスクツール」項目を新設。

検証: Kaptureで実機検証済み。Imageタブ・レイアウトタブのペイント/マスクとも、合成PointerEvent(pointerType: 'pen')で低圧力/高圧力のストロークを送り、ピクセル解析でブラシサイズ・不透明度が筆圧に応じて変化することを確認。ペイントのプレビュー分離設計は、ストローク確定前後で <image href> が更新されるタイミング(pointerupのみ)を確認。マスクのストローク途切れ修正は、pointerdown→move→(境界外へ)pointerleave→move継続→pointerup というシーケンスを送り、マスクレイヤーに途切れなく1本のストロークとして描画されること(179行にわたる連続した塗り)を確認。DB破損防御は、防御コード追加後に一覧取得がクラッシュせず完了することを確認。ヘルプ・README 3言語は node --check に加え、過去に見逃し実績のある vm.SourceTextModule での厳密構文検証も実施(今回1箇所、英語版の所有格アポストロフィのエスケープ漏れを検出・修正)。


2026-09-07(Imageタブに PSD 対応、出力タブに SVG エクスポートを追加、v1.38.0)

ユーザー依頼: ComfyUI-Workflow-Studio 側で先行実装された Image Edit タブの PSD(Photoshop) 対応を、Comic Creator の Image タブにも移植したい。続けて、レイアウトタブ(ページ全体のコマ割り編集)の内容も外部ソフトで編集できるようにしたいとの相談を受け調査、出力タブへの SVG エクスポート追加とヘルプ・README への反映まで行った。

1. Image タブの PSD (Photoshop) 対応: WFS 側の実装(d75a9c8)を参考に、バックエンド py/ccc.py に POST /api/ccc/psd/import-layers(multipart アップロード→レイヤー配列 JSON、psd-tools 使用)と POST /api/ccc/psd/export-layers(レイヤー配列 JSON→PSD バイナリ)を追加。フロントは image-tab.js に _loadPsdFile(PSD をレイヤー構成ごと新規ドキュメントとして開く。既存の作業内容とはマージしない)・_saveAsPsd/_buildPsdExportLayers(各レイヤーを回転・反転・拡縮のみベイクしたフルサイズ PNG として送信、opacity・blendMode は PSD レイヤー属性として別送り)を実装。templates/index.html に「Open PSD」「PSD」ボタンを追加。調整レイヤーは PSD が独立レイヤーとして表現できないため、直下の可視レイヤーへ効果を焼き込んでから除外する簡略化を採用(WFS 側にはこの機能自体が無く前例なし)。依存パッケージ psd-tools は新設した requirements.txt に記載(ComfyUI_5 の共有 venv には WFS 用に既にインストール済みだったため実機インストールは不要だった)。

2. レイアウトの PSD 化可能性・SVG 以外の外部編集フォーマットの調査: psd-tools の書き込み API を確認したところ、新規作成できるレイヤー種別は実質 PixelLayer(ラスター) と Group(フォルダ) のみで、テキストレイヤー・ベクターシェイプ・レイヤー効果は書き込み不可と判明。レイアウトタブは Image タブの LayerManager のような明示的なレイヤー配列を持たず、複数の SVG 文字列を buildMergedSvg() で動的合成した結果としてしか完成形が存在しないため、PSD 化は Image タブ版より実装コストが大きいと判断。Krita .kra(オープンな形式だがベクターレイヤー対応には新規実装が必要)・CLIP STUDIO PAINT .clip(仕様非公開で信頼できる書き込み手段が無い)も調査した結果、レイアウトタブは内部的に既に SVG そのもの(buildMergedSvg() が完全な SVG を、embedFontsInSvg() がフォント埋め込みまで既に実装済み=従来は内部のラスタライズ用途にのみ使用)であることから、SVG をそのままダウンロードできるようにする方針に決定。詳細は新設した PLAN_svg_export.md を参照。

3. 出力タブへの SVG エクスポート追加: 12-text-png-export.js の handleExport() に isSvg 分岐を追加し、既存の PNG/JPEG/WebP と同じ zip 保存・フォルダ書き込み・保存ダイアログの経路を共有。drawSvgOnCanvas() から SVG の width/height 属性設定ロジックを _prepareSvgForExport() として切り出して再利用(viewBox は維持するため出力サイズを変えてもベクター内容は劣化しない)。templates/index.html の #export-format に SVG を追加。

4. ユーザーによる外部ソフト実地検証: 書き出した SVG を実際に主要ソフトで開いた結果、Inkscape・Affinity Designer/Publisher はほぼそのまま再編集可能、Adobe Illustrator は画像リンクが切れ個別再指定が必要で実用性が低い、CorelDraw は読み込み時にハングアップし非対応と判明。編集後 SVG の再インポート機能は「ページ全体の自動再分割は属性依存で壊れやすい」という調査結果を踏まえ、ユーザー判断により見送り(着手しない)。

5. Image タブの「Upload」ボタンを「File Open」に改名: 併せてプレースホルダー文言("Upload an image or drop it here" → "Open a file or drop it here" 等)も統一。

6. ドキュメント更新: ヘルプタブ(日英中 3 言語、22-help-tab.js)に PSD 対応・File Open 改名・SVG エクスポート(Inkscape/Affinity/Illustrator/CorelDraw の互換性情報を含む)を反映。README(日英中 3 言語)にも同内容を反映し、requirements.txt 新設に伴い「追加の Python パッケージ不要」の記載を修正。

7. SVGインポート経路のサニタイズ漏れを修正(ユーザーからのセキュリティ観点の質問を受けて発見): SVG出力機能の追加を機に能動的コンテンツ(<script>・on*イベントハンドラ・javascript: URI)の混入経路を洗い出したところ、02-assets.jsのグループアセット挿入(insertGroupAsset)はsanitizeSvgTree()で既に無害化済みだった一方、10-output-pages.jsのimportImageAsPage()(出力タブ「+ 画像を取り込む」)はfile.type.startsWith('image/')判定がSVG(image/svg+xml)も素通りするにもかかわらず、生バイトのままdata URLとして<image href="data:...">に埋め込んでおり未サニタイズだった。<image>要素経由の読み込みはブラウザの仕様上スクリプト実行が無効化されるため実害は限定的だが、防御を1箇所に統一するため、SVGファイル取込時のみreadFileAsText→DOMParser→sanitizeSvgTree()→svgTextToDataUrlの経路に変更し無害化してから埋め込むよう修正。

検証: Kapture で実機検証済み。PSD インポート(2 レイヤーのテスト PSD → レイヤーパネルに正しく2枚表示)・エクスポート(psd-tools で再検証しレイヤー数・可視状態・合成結果が一致)、SVG エクスポート(HTMLAnchorElement.prototype.click をモンキーパッチして blob 内容を捕捉する手法で検証、<text> 要素・@font-face 埋め込みを含む正しい SVG を確認)、ヘルプタブの日英中 3 言語表示切替(コンソールエラー無し)を確認。サニタイズ修正は、<script>・onload・javascript: href を仕込んだ検証用 SVG をimportImageAsPage()へ直接投入し、生成されたページのsvgContentにこれらが一切含まれずwindow.__xssFired(検証用フラグ)も発火しないことを確認(検証用ページはテスト後に削除済み)。10-output-pages.jsが02-assets.js経由で01-state.jsと循環import(10-output-pages.js→02-assets.js→01-state.js→10-output-pages.js)になる点も確認したが、既存コードと同じく参照は関数内部(呼び出し時点で評価)に閉じているため実機で問題無く動作することを確認。


2026-09-05(3DポーズのVRMAライブラリ対応・任意フレームでのコマ確定、ライト/ライブラリボタンをEditorへ統合、v1.37.0)

ユーザー依頼: comfyui-vrm-pose-editorをv0.18.0へアップデートしたことに伴い、SPA側の「ライブラリ」ボタンをVRMAに対応させたい。再生バーで任意のフレームでコマに確定したい。続けて、「ライト」「ライブラリ」の2ボタンを「Editor」ボタンに統合(ポーズタブで開く)、「コマに配置」ボタンを「ポーズ読込」ボタン左隣へ移動したい、との依頼。

背景: v0.15.0で追加されたPose LibraryのVRMA対応(.vrmaのLoad/Load KEY)とv0.14.0で追加されたLight & Pose Editor統合モーダル(キーフレームタイムライン)は、いずれもComfyUIノード側専用のnodeActionsブリッジ(doCapture/loadVrmFile/loadVrmaFile/unloadVrma)を前提にした設計。前回セッション(9/4、v1.36.1)はopenLightPoseEditor()自体の呼び出し不整合は修復したが、「ライブラリ」ボタンは単体のopenPoseLibrary()を呼ぶ旧経路のままで、VRMAタイムライン機能には未対応だった。

実装:

  • 23-pose3d-bridge.js: 単体のopenPoseLibrary()呼び出しをやめ、統合モーダルopenLightPoseEditor()をPoseタブで開く形に統一(ノード側もv0.14.0以降、専用のPose Libraryボタンを廃止し同じ経路に一本化されているため)。これにより📚 Pose Library内のVRMAのLoad/Load KEY・キーフレームタイムライン(再生バー)がSPAからも利用可能に。
  • キーフレームパネルの「📸 Capture」ボタン(nodeActions.doCapture)に、新設した_pose3dCaptureFrameToPanel()を接続。既存の「コマに確定」(commitPose3D)と共通処理を_pose3dInsertCaptureIntoPanel()ヘルパーへ切り出しつつ、3Dビュー/モーダルを閉じない点だけを差別化(タイムラインを開いたまま任意のフレームへシークして連続してコマへ確定できるようにするため)。
  • 「ライト」「ライブラリ」の2ボタンを「🎛 Editor」1個に統合(常にPoseタブで開始、ライト設定が必要な場合はモーダル内の💡Lightタブへ切替)。「コマに配置」ボタンをモデル読込の直後・ポーズ読込の左隣へ移設。
  • ヘルプタブ(日英中)・README(日英中、3Dポーズ節とスクリーンショットギャラリーに新規2枚を追加)を更新。

vrm-pose-editor側の副次的な修正(light_editor.js): Pose Library呼び出しがnodeActionsオブジェクトの真偽のみで「⬇ Load」ボタンのコールバックを生成しており、SPAのようにdoCaptureのみを渡す部分的なnodeActionsではnodeActions.loadVrmaFileが未定義のままクラッシュする不具合を発見・修正(nodeActions?.loadVrmaFileの存在チェックに変更)。ノード自体は常に4メソッド全部入りのnodeActionsを渡すため、ノード単体での動作には影響しない。

検証: Kaptureで実機検証済み。🎛 Editor→Poseタブ→📚 Pose Library→VRMA_01選択→🔑 Load KEY(284キーフレーム取込)→▶再生→任意フレームで一時停止→📸 Captureでコマ1へ確定(レイヤーパネルに画像追加、3Dビュー・モーダルとも継続動作)を確認。ボタン再配置後のツールバー順序(モデル読込→コマに配置→ポーズ読込→…→🎛 Editor→…)も確認。検証中に作成したテスト用キャプチャ画像は作業ファイルから削除済み。


2026-09-04(vrm-pose-editorのv0.14.0改名に追従できておらず3Dポーズの「ライト」ボタンが開けなくなっていた不具合を修正、v1.36.1)

ユーザー報告: レイアウトタブ「3Dポーズ」の「ライト」ボタンを押しても何も起きない。comfyui-vrm-pose-editor(3Dポーズ機能の実体、vrm-pose-editor-architecture参照)を最近アップデートしたとのこと。

原因: comfyui-vrm-pose-editor はv0.14.0で「Light & Pose Editor統合モーダル」化に伴い、light_editor.js の起動関数を openLightEditor(editor, cvsWrapper) から openLightPoseEditor(editor, cvsWrapper, vrmBuffer, getShapeKeys, onClose, initialTab, nodeActions) へ改名・シグネチャ変更していた。SPA側のブリッジ(static/js/pose3d.js)は旧関数名 light.openLightEditor を参照したままだったため window.openLightEditor が undefined になり、23-pose3d-bridge.js のライトボタンが「エディタが見つかりません」扱いになっていた。他のブリッジ関数(initPoseEditor3D・openPoseLibrary)は同アップデートでもシグネチャが後方互換だったため影響を受けていなかった。

修正:

  • pose3d.js: window.openLightPoseEditor = light.openLightPoseEditor; に変更。
  • 23-pose3d-bridge.js: ライトボタンの呼び出しを新シグネチャに合わせて更新。vrmBufferにはstate.pose3d.modelBuffer、getShapeKeysには新設したcurrentMorphKeys(onMorphKeysReadyコールバックで保持、ノード側pose_editor_3d.jsの同名変数と同じパターン)を渡す。nodeActionsはSPAには画像キャプチャ・VRM/VRMAロードのノード固有処理が無いため省略(呼び出し側でnodeActions?.xxx?.()と安全に扱われる設計のため未指定でも問題ない)。
  • モーダル内でWind/LookAt/カメラ/Point Size等を変更できるようになったため、onCloseコールバック(_pose3dOnLightPoseEditorClosedを新設)でノード側onLightPoseEditorClosedと同じ内容をレイアウトタブのツールバーへ再同期するようにした。対応しないとモーダルを閉じた後にツールバー表示が古いまま残る新規の不具合になるところだった。
  • text3d-settings-modal.jsのコメント中の関数名参照(openLightEditor→openLightPoseEditor)も修正。

検証: Kaptureで実機検証済み。VRMモデル読込→ライトボタンでLight & Pose Editorモーダルが開くこと(Lightタブのライト一覧・プロパティ、Poseタブのシェイプキー14個表示)、モーダルを閉じてもレイアウトタブの3Dビュー・ツールバーが正常に復帰すること、コンソールにエラーが出ないこと(デフォルトモデル探索の404は既知の無害な挙動)を確認。他の3Dポーズ機能(モデル読込・ポーズライブラリ・ミラー・視線/揺れ/風トグル等)は今回のvrm-pose-editorアップデートでシグネチャ変更が無かったため影響なしとコード上でも確認済み。


2026-08-26(テキストスタイルに行間・上下位置・文字寄せを追加し実テキストへ反映、v1.36.0)

ユーザー依頼: テキストのスタイル(フォント管理タブ「スタイル」/レイアウト・Imageタブの「スタイル」モーダル、実体は共通の text-style-modal.js)のプレビュー文字を2行にし、サイズ下に行間スライダー、上下位置・文字寄せを追加したい。フキダシの内包テキスト(09f-bubble-text.js)と同様の見た目・操作にしたい。

スコープ確認: AskUserQuestionで2点を確認。(1) プレビュー表示だけでなく実テキストにも反映するか→実テキストにも反映(レイアウトタブ・Imageタブ両方)。(2) 既存の文字寄せドロップダウン(Imageタブ専用として実装済み、レイアウトタブ未適用)をボタン形式に変更するか→ボタン形式に変更し、レイアウト/Imageタブで統一する。

実装:

  • SVGテキスト(レイアウトタブ、09e-text-tool.js): _setTextElVertical を拡張し、dataset.lineHeightMult/dataset.textAlign/dataset.textValign(既定値は旧来の固定値と同一=1.2em・左寄せ・上基準で後方互換)から複数行tspanのdy/dx(すべて'em'相対単位。フォントサイズ変更時に行間比率が自動追従するようリサイズハンドルの挙動を壊さないため)とtext-anchorを再構築するよう変更。_fontMgrApplyStyleAttrsToTextEl(スタイル適用の共通入口)にdataset設定+tspan未生成時の改行分割フォールバックを追加し、_fontMgrExtractStyleFromTextEl(モーダル再オープン時の初期値復元)にも新フィールドの読み取りを追加。
  • Canvasテキスト(Imageタブ、image-tab.js): _measureTextBox/_rerenderTextLayer の行間をp.lineHeightMult可変化。_applyTextPropsToLayerのレイヤー高さ変化時の基準点をvalign(top/center/bottom)に応じて可変化(従来は常に中心固定)。
  • UI: フォント管理タブ(19-font-manager.js、templates/index.html)とtext-style-modal.jsの両方で、プレビュー文字欄を<textarea>化(既定値2行)、サイズ下に行間スライダー追加、文字寄せセレクトをフキダシと同じボタン形式+上下位置ボタンに置き換え。i18n(日英中3言語)追加。

副次的に発見・修正した不具合(Imageタブの行間クリッピング): 実機検証中、ユーザーからImageタブで行間を0.8(1.2未満)にすると文字下部が切れるとの報告。原因は_measureTextBoxのCanvas高さ計算が「行数×行間」のみで、行間を1.2未満にすると1行分のグリフ自体の実高さ(降下部分等)より確保領域が小さくなっていたため。最終行の実高さ分は常にfontSize×1.2(従来固定値)で確保し、行間はそれ以外の行の間隔にのみ適用するよう修正。

検証: Kaptureで実機検証(本セッション途中でKaptureとの接続が切断されたため、Imageタブのクリッピング修正の再確認はnodeのvm.SourceTextModuleによるESM構文検証のみ)。レイアウトタブでは、実テキスト作成→行間2.0/文字寄せ右/上下位置中央でスタイル適用→tspanのdy/x/text-anchor・getBBoxによるベースライン位置を数値検証(想定通り、上下位置=centerで2行のベースライン中点が元のy座標と一致することを確認)、縦書き切替でもクラッシュしないことを確認。Imageタブでは、行間0.8適用前後のスクリーンショットで文字欠けが解消されたことを確認。i18n・ヘルプ・README更新済み。


2026-08-26(ページ管理タブのテンプレートに「ページとして挿入」ボタンを追加、v1.36.0)

ユーザー依頼: ページタブの「テンプレート」サブタブで選択したテンプレートを、開いている作品へ新規ページとして挿入できるようにしたい(レイアウトタブのアセットパネル「テンプレート」タブの機能と同じ)。

実装: グループ絞り込みドロップダウンの右隣にボタンを追加し、既存のinsertTemplatePageToWork()(アセットパネル版と共通の関数、11a-work-manager.js)をそのまま呼び出すだけで実現。06b-template-manager.jsから11a-work-manager.jsへの新規import(循環importになるが、参照は関数内部に閉じているため既存のG1/G2と同じ判断基準で安全)。選択・削除・リネーム時のボタン有効/無効化は既存の削除・名前変更ボタンと同様に同期。

検証: Kaptureで実機検証済み。テンプレート「2」を選択→ボタン押下→開いていた作品にページ3/3として追加されることを確認。


2026-08-26(3DポーズにカメラFOV・Nearスライダーを追加、v1.36.0)

ユーザー依頼: vrm-pose-editor側のアップデートでカメラのFOV・Nearに対応したので、レイアウトタブの3Dポーズにも反映したい(「コマに配置」ボタン左隣にスライダーを設置)。

実装: pose_editor_core.js(comfyui-vrm-pose-editor、共有コア)側は既にFOV/Near設定用のsetFov/getFov/setNear/getNearをpublic APIとして持っていたため、SPA側(templates/index.html、23-pose3d-bridge.js)はUIスライダー追加とイベント配線のみで対応(FOV: 10〜120°・既定45、Near: 0.01〜5・既定0.1、ComfyUIノード側UIと同じ値域)。i18n(日英中3言語)追加。

検証: Kaptureで実機検証済み。VRMモデル読込後、FOVスライダーを45→110→45で見た目のズーム変化を確認、NearスライダーをNear=3にしてカメラより近いオブジェクトが正しくクリップされることを確認。


2026-08-25(Generateモーダルのプロンプトタブでサブコマがコマと同じラベルになり判別できない不具合を修正、v1.35.0)

ユーザーから、コマ1に複数のサブコマがあるページでGenerateモーダルを開くと、コマとサブコマのタブ名が同じ「コマN」形式になり判別できないとの報告。

原因: プロンプトタブ生成処理(15-pixifx-bridge.js の openLayoutI2IModal)が state.activePage.panels(サブコマも parentPanelId 付きの通常エントリとして混在)を単純に配列順で走査し、種別を区別せず同じ layout.i2iTabPanel(「コマN」)ラベルを割り当てていたため。

修正: parentPanelId の有無で通常コマとサブコマを別カウンタに分離し、サブコマには新設した layout.i2iTabSubPanel(「サブコマN」)ラベルを割り当てるよう変更。i18n(日英中3言語、英語は"Sub-panel N"、中国語は既存の「子画格」表記に合わせ「子画格N」)・ヘルプを更新。

検証: node --check および vm.SourceTextModule によるESM構文検証のみ実施。comic-creatorはシンボリックリンクのため変更は即反映されるが、実機でのKapture検証は未実施。


2026-08-25(レイアウトタブのI2Iモーダルを「Generate」へ刷新、対象追加・コマ別プロンプトタブ・一括生成・T2I対応、v1.35.0)

ユーザー依頼: レイアウトタブのI2Iモーダルを改善したい。Runボタンを対象行右端へ、全体/コマ別にプロンプトをタブ化、I2I設定のワークフローファイル指定を折りたたみに、T2Iを利用可能に、モーダル名・ボタン名をI2IからGenerateへ変更。

設計方針: 対象=ページ全体時の一括生成方法(batchチェックボックス)や対象=選択タブ時のプロンプト合成方法など複数の設計判断が必要だったため、AskUserQuestionで段階的に確認しながら合意形成した。最終仕様: 対象は「選択画像」「ページ全体」「選択タブ」の3択。batchは対象=ページ全体のときのみ表示される処理方法選択(対象が選択画像/選択タブの場合は非表示)。「選択タブ」対象は選択中タブのプロンプトのみを使用(「全体」タブとは合成しない)。「ページ全体」+batch ONは全コマ一括生成(「全体」+各コマタブのプロンプトを合成、半自動マンガ作成のバッチ生成と同じ設計)。batch OFFは従来通りページ全体を1枚に合成しオーバーレイへ挿入(ラフ用途)。

実装:

  • 15-pixifx-bridge.js の openLayoutI2IModal を全面刷新。Run/T2Iチェックボックス/batchチェックボックスを対象行に集約し、プロンプトタブ(全体+コマN)をタブごとに独立保持(_layoutI2IPrompts)、対象×T2I×batchの組み合わせごとに _li2iRunSelected/_li2iRunWholePage/_li2iRunPanelTab/_li2iRunBatch の4関数へ分岐。
  • T2Iは半自動マンガ作成で使われている pickSdxlResolution(auto-comic-core.js)・requestPanelImageFromWorkflowStudio(14-integrations.js、T2I用デフォルトワークフロー設定と対)をそのまま再利用。
  • コマ単位の画像取得(_getPanelImageBlob)とプロンプト合成(_composeOverallPrompt)は元々 26-auto-comic-bridge.js にのみ存在したため、15-pixifx-bridge.js 側へ移設・エクスポートし、26-auto-comic-bridge.js はそちらをimportするよう変更(重複コード解消)。
  • ワークフロー設定を <details>/<summary>(3Dポーズのシェイプキー折りたたみと同じ既存パターン)で折りたたみ化し、I2I用・T2I用のデフォルトワークフロー設定を個別に配置。
  • モーダル見出し・開くボタンのラベルをi18n経由で「I2I」→「Generate」に変更(内部の関数名・CSS/要素idプレフィックス li2i- は変更範囲外として維持)。

検証: node --check・vm.SourceTextModule によるESM構文検証のみ実施。実機でのKapture検証は未実施。i18n(日英中3言語)・ヘルプ・README更新済み。


2026-08-25(テンプレート作成ウィザードに「デザインテンプレート」作成モードを追加、v1.35.0)

ユーザー依頼: ページタブのテンプレート作成に、コマ外・コマ間の枠が一切ないデザイン用テンプレート(チラシ作成などの用途)を追加したい。

実装: 設定画面に「デザイン」チェックボックスを追加(06c-template-wizard.js の _tmplWizSetDesignMode)。ONにするとフレーム幅入力欄を 0 に固定して無効化し、モーダル見出しを「デザインテンプレートを作成」に切り替える。分割ロジック自体(_splitPolygonByLine の gap パラメータ)は元々フレーム幅0を「余白/ガター無し」としてそのまま扱える設計だったため、UIトグルの追加のみで実現できた。保存時のデフォルト名も「デザインテンプレート_...」に変更し通常テンプレートと区別しやすくした。

副次的に、design modeをOFFに戻した際、前回セッションのフレーム幅入力値が残っていて次回オープン時にデフォルト値の代わりに使われてしまうバグを実装中に発見し、openTemplateWizard() で dataset.prevValue を明示的に破棄するよう修正した。

検証: node --check・vm.SourceTextModule によるESM構文検証のみ実施。実機でのKapture検証は未実施。i18n(日英中3言語)・ヘルプ・README更新済み。


2026-08-25(Imageタブの保存/送信ボタンを整理しLI Node機能を追加、Workflow StudioのSave to Inputボタンを削除、v1.35.0)

ユーザー依頼: Imageタブ上部のSave/Sendボタン名を整理したい(Save: PNG/Project/Gallery、Send to: Eagle/wsI2I/LI Node/レイアウト)。またworkflow studioのImage EditタブのSend to Workflow機能を追加したい(既存のSend to ComfyUIは削除)。

調査結果: workflow studio側の「Send to Workflow」機能は既に実装済み(過去のDEVLOG記録通り)だったため新規実装は不要と判明。今回の対応範囲は実質的に (1) comic-creator Imageタブのボタン整理・LI Node新規追加、(2) workflow studio側の冗長になった「Save to Input」(旧Send to ComfyUI)ボタンの削除、の2点。

実装:

  • comic-creator Imageタブのアクションバーを区切り線で Save系(PNG/Project/Gallery)と Send to系(Eagle/wsI2I/LI Node/レイアウト)に整理。「Send to ComfyUI」ボタンと _uploadToComfyUI() を削除。
  • 新規「LI Node」ボタンを追加(image-tab.js の _sendToLoadImageNode())。comic-creatorはComfyUIの「CC」ボタンから window.open() で開かれるポップアップのため window.opener =ComfyUI本体である性質を利用し、window.opener.wfmSendImageToSelectedNode(web/comfyui/node_sets_menu.js)経由でComfyUIキャンバス上の選択中ノードへ合成結果を直接送信する。Workflow Studio Image Editタブの「Send to Workflow」(FileExport.sendToWorkflow)と全く同じブリッジ機構を再利用。
  • ComfyUI-Workflow-Studio側の「Save to Input」ボタン(ie-upload-comfy-btn)と uploadToComfyUI() を削除。実行時コピー(StabilityMatrix custom_nodes 配下、シンボリックリンクではなく通常コピー)へも変更を同期しハッシュ一致を確認。

検証: node --check・vm.SourceTextModule によるESM構文検証のみ実施。実機でのKapture検証は未実施。i18n(日英中3言語)・ヘルプ・README(comic-creator: 3言語、Workflow Studio: 英語)更新済み。


2026-08-13(SVGインポート時のコマ枠線幅不一致を解消するスケール補正機能を追加、副次的に見つかった2件の不具合も修正、v1.33.0)

ユーザーから「テンプレートを作成」ウィザードで作ったテンプレートと、InkscapeのSVGからテンプレート作成したものとでコマ枠線幅の太さが揃わない、線幅を100超にすると内側に白線が増える、との報告(スクリーンショット添付)。

原因: テンプレートウィザードの内部座標系は「1ユーザー単位 = 0.01mm」固定(A4 = 21000×29700)だが、Inkscape等の外部SVGはツールごとに実寸(mm)とviewBoxユーザー単位の対応関係が異なる(例: Inkscapeは96dpiのpx単位で出力するため1ユーザー単位≒0.2646mm、テンプレートウィザードとは約26.5倍のスケール差)。parseSVGForTemplate(06c-template-wizard.js)はこの差を正規化せずに座標をそのまま取り込んでいたため、同じ線幅数値でも実際の太さが大きく異なっていた。線幅を大きくして帳尻を合わせようとすると、実際のコマ間の隙間(Inkscape座標系では数値が小さい)に対してinset量が過大になり、clipPathの縮小ポリゴンが破綻して内側に白線が生じていた。

修正: SVGインポート時、ルート<svg>要素のwidth属性(実寸・単位付き)とpanel_0の外接矩形から、内部標準スケールへ正規化する倍率を自動算出(_tmplComputeSuggestedScale)。1から2%以上ずれている場合は確認ダイアログを表示し、算出した倍率をデフォルト値として確認・補正できるようにした(適用するとコマ座標・ページサイズ・svgContent内の座標とstroke-widthを内部標準スケールへ変換、_tmplApplyImportScale)。

副次的に発見・修正した不具合1(コマ枠線幅が反映されない): 上記の対応中、rect/path形式のテンプレート(Inkscape等)で「コマ枠線幅を変えても表示が変化しない」ことが判明。_tmplTemplateToPageSvgString(rect/path形式テンプレートをpolygonへ合成する、2026-08-13に追加されたばかりの処理)が、コマのpolygon idをpanel_${number}という決め打ちで生成していたのに対し、ページ側のpanels[].idは元のInkscape要素id(rect4等)のままだった。コマ枠線幅の変更処理(07-pages.js)はこのidでsvg内のpolygonを検索するため、一致せず常にスキップされていた。idをp.id(元のid)に統一して修正。この修正により、renderLayoutTabのpanel-border-width欄の初期値表示(同じid不一致が原因で機能していなかった)も同時に解消された。

副次的に発見・修正した不具合2(Cancelしても無確認で上書き): ユーザーから「補正ダイアログでCancelしても『作成した』表示になる(実際には作成されていない)」との報告。調査したところ、同名テンプレートへの無確認上書きが原因と判明(同名のInkscapeテンプレートを繰り返しインポートしていたため、Cancelを押しても保存自体は実行され、既存の同名テンプレートが上書きされて新規作成されたようには見えなかった)。ユーザーの希望を確認の上、以下に変更: (1) スケール確認ダイアログでCancelを押すとインポート自体を中止(保存しない)、(2) 同名テンプレートが既に存在する場合は上書きせず、別の名前の入力を求める(キャンセルまたは空欄で確定するとインポート中止)。

Kaptureで実機検証済み: jsdomでのスケール算出ロジック単体テスト(Ink_2panel.svg/Ink_3panel.svgで26.46倍・26.58倍と正しく算出、適用後は幅21000・線幅0.5mm→内部値50に正規化されることを確認)に加え、実機で未補正テンプレート(ink_2panel、793.7×1122.5、線幅計算が4という極端に細い値になることを確認)とスケール補正済みテンプレート(21000×29700、線幅63)の両方でページを作成し、panel-border-width欄を63→300に変更してsvg内のpolygonにstyle="stroke-width:300"が正しく反映されること・視覚的に枠線が太くなることを確認。ヘルプ(22-help-tab.js、日英中3言語)・README(3言語)を更新済み。ヘルプ更新時、「コマは<polygon>のみ対応」という現状と矛盾する古い記述(付録: テンプレートSVG仕様)と、「viewBoxをそのまま読み込むため通常は問題ない」という今回の修正と正反対の古い記述(付録: Inkscapeでテンプレート作成のトラブルシューティング)も合わせて修正した。


2026-08-13(レイアウトのページ送りが作品を閉じた後・新規作品作成直後に他作品のページへ切り替わる不具合を修正、v1.33.0)

上記のテンプレート対応と並行して、ユーザーから「レイアウトで作品を閉じたがページを送ると他の作品が表示される。新規で作成してレイアウトを確認すると同様にページが存在している。しかしアセットパネルのPタブにはこの作品にページがありませんとなっている」との報告。

原因: _layoutPageList()(07-pages.js)が「作品が未選択、またはその作品にページが1件も無い場合」に、全作品の全ページを名前順にまとめたリストへフォールバックしていた。作品を閉じるとstate.activeWorkがnullになり、新規作品作成直後はページがまだ0件のため、いずれのケースでもこのフォールバックが発動し、ページ送りボタンが無効化されずに押すと他の作品のページへ切り替わっていた。一方、ページ管理側は「ページは必ず作品・stock・任意グループのいずれかに属する運用」(無所属ページは起動時に自動でstockへ回収、11a-work-manager.js)という設計になっており、この「全ページ」フォールバックは元々不要かつ有害だった(アセットパネルのPタブは正しく作品スコープで表示していたため、レイアウトタブのページ送りだけがこのバグを持っていた)。

修正: _layoutPageList()から全ページへのフォールバックを削除し、作品未選択・ページ0件では空リストを返すよう変更。

Kaptureで実機検証済み: 2ページある作品を開いて1 / 2表示を確認後、作品を閉じると- / -表示・前へ/次へボタンともdisabledになることを確認(修正前はここが問題だった)。新規作品作成(ページ0件)でも同様に- / -・両ボタンdisabledを確認。検証用に作成した作品は削除済み。


2026-08-12(rect/polygon混在テンプレートで一部コマだけ枠線幅が反映されない不具合を修正、v1.33.0)

上記の修正(CSSクラスのstroke優先度問題)をユーザーが新規作成したファイル(corel_svg10.svg/corel_svg11.svg、UTF-8、panel_1・panel_2は<rect>、panel_3のみ曲線的な形状のため<polygon>という混在構成)で確認したところ、「コマは問題ないが、コマ枠線幅がコマ3にしか効かない」と報告(スクリーンショット添付)。

原因: _tmplResolveTemplateSvgForPage(ページ生成時に元のsvgContentをそのまま使うか、保存済み座標から合成したSVGにフォールバックするかを判定)が「<polygon>が1つでも含まれていれば元のsvgContentをそのまま使う」という判定になっていた。_prepareTemplateSvgDocForPageや線幅変更処理は<polygon>要素しか見ないため、rect製のpanel_1・panel_2はそもそも処理対象に含まれず、polygon製のpanel_3だけに線幅変更が反映されていた。

修正: 判定基準を「<polygon>の数が全コマ数(panel_0+panels全て)以上あるか」に変更(_tmplSvgHasPolygons → _tmplSvgHasEnoughPolygonsにリネームし、必要数を引数で受け取るように変更)。一部だけrect/pathが混ざるテンプレートは合成SVGへ確実にフォールバックされるようになった。

Kaptureで実機検証済み: panel_0/1/2をrect、panel_3のみpolygonという混在SVGを再現し、修正前は判定通り合成フォールバックされず一部のコマの線幅が変更されないことを確認した上で修正を適用。修正後は合成フォールバックが正しく発動し、全コマ(panel_0の非表示化含む)にgetComputedStyle()で線幅80pxが反映されることを確認。既存の全回帰テスト(純粋polygon形式は引き続き元のsvgContentをそのまま使う=CorelDraw/ウィザード生成テンプレートの見た目を変えない、純粋rect形式は引き続き合成フォールバック)も再実行し正常。


2026-08-12(CorelDraw/Affinity実ファイルの実機確認で見つかったテンプレート取込みの追加不具合4件を修正、v1.33.0)

上記のテンプレートSVG互換性拡張について、ユーザーが実際にCorelDraw・Affinity Designerで作成したファイル(coreldraw/・affinity/フォルダに計7ファイル)で確認したところ、「CorelDrawは矩形でサイズが小さくなり、パスではコマがおかしな状態になる(コマに配置できない)」「Affinityもサイズが小さくなる」との報告を受け、実ファイルを直接調査。当初は文字コード(CorelDrawはUTF-16 BOM付き)を疑ったが、FileReader.readAsText(file,'utf-8')は実際にはBOMを見て正しくUTF-16として復号することを実機で確認し否定。真因は以下4件だった。

1. ページサイズとコマ座標のスケール不一致(CorelDraw): template.width/heightをviewBoxの生数値から直接取得していたが、コマ座標はgetCTM()(ページ外枠panel_0はページのCSSピクセルサイズ基準で解決される仕様)経由で取得していたため、viewBoxの数値と実際のCSSピクセルサイズが一致しないSVG(CorelDrawはwidth="210mm" height="297mm"に対しviewBox="0 0 21000 29700"という独自スケールを使用)で、ページは21000×29700なのにコマ座標は793×1122付近に収まるという致命的な不整合が発生していた。これまでのInkscape等のテストファイルはviewBoxの数値が96dpi換算のCSSピクセル値とほぼ一致していたため気づけなかった。修正: template.width/heightを、コマ座標と同じgetCTM()解決結果であるpanel_0(ページ外枠)の外接矩形から算出するよう変更(_tmplBoundingBoxOfPointsStrを新設)。

2. width="100%" height="100%"によるサイズ縮小(Affinity): width/height属性が完全に無い場合はviewBoxがそのまま実寸として使われるが、明示的に100%指定されている場合は、テンプレート解析用の非表示コンテナに具体的なサイズが無いためCSSの「置換要素のデフォルトサイズ」(300×150にアスペクト比を保って収める)にフォールバックしてしまい、意図しない小さいサイズになっていた。修正: width/heightが%指定の場合、解析前にviewBoxの数値をpxとして明示的に設定し直す処理を_tmplAttachHiddenSvgに追加。

3. コマ番号のズレ(Affinity): Affinityはtransform付きレイヤーを<g id="panel_2" transform="matrix(...)">のようにグループでラップし、中の<rect>自体にはidを付与しない場合がある。コマ番号の判定が図形自身のid/labelしか見ていなかったため、[1, 1, 2]のように番号がズレていた。修正: 図形自身にid/labelが無い場合、祖先方向に最も近いid/label付き要素を採用するよう_tmplEffectiveId/_tmplGetLabelを拡張。

4. panel_0の枠線が消えない・コマの線幅が反映されない(CorelDraw path/polygon版): 上記のスケール修正後、ユーザーから「コマ自体は機能するが、不明な枠の表示、枠線幅がコマに適用されない」と追加報告。CorelDrawは<defs><style>内のCSSクラス(.str0{stroke:black;stroke-width:35.27})でストロークを指定しており、各図形はclass="fil0 str0"のみでインラインstyle属性を持たない。ページ生成時の「panel_0の枠線を非表示にする」処理も「コマの線幅をユーザー指定値に変更する」処理も、プレゼンテーション属性(setAttribute('stroke-width',...))しか書き換えておらず、かつ「既にインラインstyleがある場合のみ」styleも書き換える実装だったため、CSS優先順位(クラスセレクタ>プレゼンテーション属性)によりクラス側の指定が常に勝ち、panel_0の枠線が消えず・コマの線幅も常に元の値のままだった。修正: 常にインラインstyle(CSS優先順位が最も高い)を強制設定する共通ヘルパー_tmplForceInlineStyleを新設し、両処理をこれ経由に統一(06c-template-wizard.js・07-pages.js)。

Kaptureで実機検証済み: CorelDraw実ファイル4種・Affinity実ファイル3種(残る2ファイルはベクター図形を含まない背景ラスター画像のみだったため、既存の「コマが見つかりません」エラーが正しく出ることを確認。ユーザーへは再エクスポートを依頼)をバイト列そのまま読み込ませ、正しいページサイズ・コマ数・番号を確認。CSSクラス構造を再現したテストでgetComputedStyle()によりpanel_0の実際の描画スタイルがstroke:noneになること、コマ線幅が指定値に正しく上書きされることを確認。既存の全回帰テスト(Inkscape・後方互換ケース等)も再実行し正常。ユーザーからスクリーンショット付きで問題再現の報告を受け、原因特定・修正・再検証まで実施。


2026-08-12(SVGテンプレートインポートの互換性を拡張してInkscape/Illustrator/Affinity Designer/CorelDrawに対応、v1.33.0)

ユーザーがInkscapeで作成した複数パターンのテンプレートSVG(矩形ツール・ベジェ/直線ツール・多角形/星ツールでそれぞれ作成)がいずれも読み込めない、との報告を受けて調査。

原因: parseSVGForTemplate(06c-template-wizard.js)が<polygon>要素のみを認識しており、Inkscapeの矩形ツール(<rect>)・ベジェ/直線ツール(<path>のH/V/Z)・多角形/星ツール(<path sodipodi:type="star">、名前は「多角形」でも実体はpath)のいずれで描いても読み込めなかった。CorelDrawだけ動作していたのは、たまたま<polygon>を直接出力する実装だったため。

修正: rect/polygon/polyline/path/circle/ellipseのいずれの要素でもコマとして読み込めるよう拡張。transform(入れ子<g>のtranslate/matrix/rotate等)の解決は、自前で行列計算するよりブラウザ本体のSVGエンジン(getCTM())に任せる方が各ツール固有の癖(sodipodi:type=star等)に対して確実なため、要素を画面外の非表示コンテナに一時アタッチしてから座標を取得する方式にした。id/label(panel_0/panel_N)による明示指定、ストローク不可視かつ名前指定の無い図形(装飾用の背景矩形等)の自動除外にも対応。

曲線・角の崩れ対策: 当初pathの座標抽出をgetPointAtLengthの等間隔サンプリングで実装したところ、直線区間の角が斜めに削れる不具合が発生(ユーザーが実際にInkscapeファイルを読み込んで報告し発覚)。M/L/H/V/Zコマンドは座標を直接算出する専用パーサー(_tmplParsePathD)に切り替え、C/S/Q/T/A等の曲線コマンドのみ、そのコマンド単体の一時的な<path>をブラウザに解釈させてgetPointAtLengthでフラット化する方式に修正し、直線の角を厳密な直角に保ちつつ曲線も正しく近似できるようにした。

副次的に発見・修正した回帰と既存の限界: 上記の拡張により、アプリ内蔵の「テンプレートを作成」ウィザードが出力する背景用の装飾矩形(<rect>、ストロークなし)が誤ってコマとして誤認識される回帰を自己検証中に発見。ストローク可視性による除外ロジックで対応した(除外しすぎて0件になった場合は装飾判定なしで再抽出する安全弁付き)。また、テンプレートからページを新規作成する処理(_prepareTemplateSvgDocForPage・_scaleSvgElementTree)が従来から<polygon>前提の実装だったため、rect/path由来のテンプレートではpanel_0の枠線非表示・コマ線幅変更・作品サイズへのスケーリングが正しく動作しない問題も発見。_tmplResolveTemplateSvgForPageを新設し、元のsvgContentが<polygon>を含まない場合のみ、保存済みのコマ座標(panels[].points/basePanelPoints)から正規化済みSVGを合成してフォールバックする方式にした(<polygon>を含む既存テンプレートは元のsvgContentをそのまま使うため、CorelDraw・ウィザード生成テンプレートの既存の見た目には影響しない)。

Kaptureで実機検証済み: ユーザー提供の6ファイル(Inkscape純正/プレーン ×矩形・直線path・多角形ツールpath)全てで正しいコマ数・厳密な直角座標を確認、既存のpolygon形式(後方互換)・defs内ダミー図形の除外・panel_0の描画順序に依存しない明示指定・装飾用背景矩形の除外・角丸矩形(曲線混在)を個別ケースとして検証。ユーザーからも「角の改善を確認しました」と確認済み。日本語ヘルプ(22-help-tab.jsの_HELP_DATA)とi18nエラーメッセージ(ja/en/zh)は実態に合わせて更新済みだが、英語・中国語版ヘルプの詳細手順(付録: Inkscapeでテンプレート作成)は未更新のため次回対応が必要。


2026-08-12(オーバーレイへのファイルドロップに対応、移動・複製・削除の不具合を修正、v1.33.0)

上記のテンプレート対応に続けて、ユーザーから2件の報告を受けて対応。

1. オーバーレイへのファイルドロップに対応(08-panels-images.jsのinitDragAndDrop): 従来はドロップ地点直下のコマ(data-panel-idを持つ要素)しか検出しておらず、コマの外(オーバーレイ含む)にドロップすると「コマを選択してください」で失敗していた。ドロップ地点がオーバーレイ上の既存コンテンツなら明示的にオーバーレイへ切り替え、コマの当たり判定を持たないオーバーレイの空白部分へのドロップは、レイヤーパネルで事前に選択済みの状態(state.selectedOverlay)に従うようにした。

2. オーバーレイ⇔コマ間の移動・複製ができない/複製になってしまう不具合: ユーザー報告「オーバーレイからコマに移動できないケースがある、もしくは複製になってしまう。移動できた場合、複製元の位置が変わる」を調査。原因: duplicateSelectedObject/moveSelectedObject(05-groups-move.js)が画像をオーバーレイへ複製・移動する際、data-panel-id属性の更新を怠っていた(下書きレイヤーへの移動では正しく'__draft__'に更新していたのに、オーバーレイだけ抜けていた)。この結果、オーバーレイに来た画像が元のコマIDを持ったまま残り、次にその画像を移動しようとした際、移動元コマの判定(同じくこの属性を最優先で参照していた)を誤り、「同じコマへの移動」と誤判定されて失敗する、あるいは保存先を取り違えて元の場所にもデータが残ってしまう(=複製に見える)不具合になっていた。修正: オーバーレイへの複製・移動時にdata-panel-id="__overlay__"を正しく設定し、あわせて移動元・複製元コマの判定ロジックも、属性ではなく実際のDOM構造(.closest('g[data-clip-panel], g[data-overlay-layer], g[data-draft-layer]'))を優先する堅牢な方式に変更した。

3. 削除したオブジェクトがドロップ後の再描画で復活する不具合: 上記2の修正確認後、ユーザーから追加報告「以前改善したドロップ時、削除したオブジェクトが復元してしまう現象が一度発生した」を受けて調査したところ、deleteSelectedObject(同ファイル)の画像削除時のコマID判定にも同じ弱点があった(g[data-clip-panel]のみ.closest()でチェックし、オーバーレイ/下書きレイヤーは.closest()を経ずにdata-panel-id属性へフォールバックしていた)。今回までの一連の修正より前に生成された古いdata-panel-idを持つオーバーレイ画像を削除すると、削除自体はDOM上成功するが保存先を誤り、オーバーレイの永続化データには反映されないままになる。その後何らかの理由でrenderLayoutTab()が呼ばれる(今回はドロップ後の再描画)と、DBの古いデータから再構築され、削除したはずのオブジェクトが復活していた。deleteSelectedObjectのimage/group種別いずれの判定にも上記2と同じロジックを適用した。

Kaptureで実機検証済み: (1)実際のUIでオーバーレイへのファイルドロップを確認、(2)画像をpanel-1→オーバーレイ→panel-2と2回移動させ複製されないこと・DB永続化データが正しく更新されることを確認、(3)人為的に古いdata-panel-idを持つオーバーレイ画像を用意し、削除→再描画をシミュレートして復活しないこと・DB上のオーバーレイデータからも画像が消えていることを確認。1・2はユーザーからも「オーバーレイへのドロップ確認しました」「移動、複製の正常動作確認しました」と確認済み。3(削除復活バグ)はユーザー未確認(「確認は時間を取って行いたい、問題があれば別途連絡する」とのこと)のため、次回セッションで指摘があれば優先的に対応すること。


2026-08-09(フキダシ内包テキストのモーダル拡充・バクダンに内側カーブパラメータを追加、v1.32.0)

ユーザー要望を受けて対応。

バクダン形状に「内側カーブ」パラメータを追加(09b-balloon-shapes.js): 従来generateBombPath()の本体パスは谷(トゲの間の凹角)・峰(トゲの先端)をすべて直線で結ぶポリゴンだった。トゲの先端は鋭いまま維持しつつ谷とその手前側面を丸められるよう_h2RoundValleysInClosedPath()を新設。谷の頂点を二次ベジエ曲線の制御点として使い、頂点そのものへは到達させず前後の辺の一部(curveAmt = spikeCurve/100×0.5、最大で各辺の半分)をQ曲線でつなぐことで、峰へは直線のまま到達させて鋭さを保つ。0〜100のスライダー(h2-spike-curve、デフォルト0=従来通りの直線的なギザギザ)で調整でき、dataset.spikeCurveとして保存される(属性が無い既存のバクダン型フキダシは0扱いのため見た目に影響なし)。

「テキストを内包」モーダルに行間スライダーを追加(09f-bubble-text.js): 従来fontSizeSvg * 1.4固定だった行間をdataset.lineHeightMult(デフォルト1.4)で可変にし、0.8〜3.0のスライダーで調整できるようにした(_bubbleTextRenderText)。

「テキストを内包」モーダルに太字チェックボックスを追加: 文字色セレクトの右隣に配置し、dataset.textBoldを内包テキストの<text>要素のfont-weight属性へ反映する。

整列ボタンのレイアウト変更(ユーザー要望): 「文字寄せ(左/中央/右)」と「上下寄せ(上/中央/下)」が別々の行に分かれていたのを、上下寄せグループの右隣に文字寄せグループを並べて1行にまとめた(要素のIDやイベント配線は変更せず、HTML内の配置順のみ入れ替え)。中央ボタンは縦軸・横軸それぞれ独立のまま2つ維持する案をユーザーが選択。

3言語(ja/en/zh)のヘルプ(22-help-tab.jsの_HELP_DATAと_HELP_I18N.en/.zh)・README(README.md/README_en.md/README_zh.md)を更新済み。バクダン内側カーブ・行間スライダー・太字+整列レイアウトの3点ともユーザー確認済み(「確認しました。問題ありません」)。node --checkに加え、ヘルプの文字列リテラル編集はvm.SourceTextModuleによるESM構文検証も実施。Kaptureでの実機確認は未実施のため、次回セッションで実機E2Eの確認を推奨。下記の画像削除復活バグ修正とあわせてv1.32.0としてリリース。


2026-08-09(レイアウトタブでコマの画像を削除→別画像をドロップすると削除済み画像が復活する不具合を修正、v1.32.0)

ユーザー報告「コマにある画像を削除後、別の画像をコマにドロップしたところ削除した画像が復活した。再度削除→別コマにドロップしても復活した」を受けて調査。

原因は複数の問題が絡んでいた:

  1. ドロップ処理が実際の落下位置を見ていなかった(08-panels-images.jsのinitDragAndDrop): 画像ファイルをドロップした際、対象コマをstate.selectedPanelId(=直前にクリックして選択していたコマ)で決めていた。ドラッグ&ドロップ自体はクリックを発生させないため選択状態が変わらず、「別のコマにドロップした」つもりでも実際には直前に画像を削除したコマへ挿入されていた。
  2. 削除処理がdata-panel-id属性だけを信用していた(05-groups-move.jsのdeleteSelectedObject、04b-layer-panel-render.jsのレイヤーパネル✕/🔓/👁ボタン): 画像削除の保存先コマ判定に、DOM上の実際の親要素(closest('g[data-clip-panel]'))ではなく画像要素のdata-panel-id属性を優先して使っていた。属性が実際の所属コマとズレていた場合、el.remove()でDOM上は消えても保存は別コマのデータを更新してしまい、削除がpanelSvgContentに反映されない。
  3. 保存失敗が静かに握りつぶされていた(07-pages.jsのsavePanelSvg): dbPut失敗時にコンソールへのログのみでユーザーへの通知が無く、state.activePageも更新されないまま残っていた。
  4. サムネイル遅延書き込みの競合(00-db.js): _scheduleThumbUpdateの600msデバウンス書き込みが、直後の即時書き込み(deferThumb未指定)でキャンセルされておらず、短時間の連続操作で古いスナップショットが後から上書きしてしまう余地があった。

修正: (1)ドロップ座標の直下にあるコマをdocument.elementFromPointで判定しselectPanel()する、(2)削除・ロック・表示切替の全箇所でDOM上の実際の親コマを最優先にする、(3)savePanelSvgの失敗時にユーザーへalertで通知する、(4)即時書き込み時に保留中のデバウンスタイマーを取り消す、の4点をそれぞれ実施。

ユーザー確認済み(「確認しました。問題ありません」)。Kaptureでの実機確認は未実施。node --checkのみ実施。上記のフキダシ内包テキスト拡充とあわせてv1.32.0としてリリース。


2026-08-08(レイアウトタブで削除したオブジェクトがタブ切替で復活する不具合を修正、v1.31.1)

ユーザー報告「レイアウトタブで削除を行ってもページタブからレイアウトタブに戻った際に削除したオブジェクトが戻る場合がある」を受けて調査。

原因: オブジェクト削除(deleteSelectedObject()=05-groups-move.js、レイヤーパネルの✕ボタン=04b-layer-panel-render.js)はDOMから要素を削除した後、savePanelSvg/saveOverlaySvg(07-pages.js/09b-balloon-shapes.js)経由で必ずawait付きでDB保存まで行っており、保存自体は正しく行われていた。またstate.activePageの読み取り→dbPut→反映という一連の処理は_enqueueActivePageSave(00-db.js)というキューで直列化されており、保存同士が競合してデータが消える問題は既に対策済みだった(00-db.js内の既存コメントに、まさにこの「削除したはずのレイヤーが復活する」現象への言及があった)。しかし、レイアウトタブへの再入場時に呼ばれるrenderLayoutTab()(07-pages.js)が、このキューを待たずに直接dbGet('pages', ...)でDBから読み直していたため、削除の保存(dbPut)がまだコミットされる前にページタブ→レイアウトタブへ素早く切り替えると、削除前の古いレコードを読み込んでしまい、削除したはずのオブジェクトが復活して見えるという「保存とは独立した経路からの読み込みが保存の完了を待たない」競合状態だった。

修正: 00-db.jsに、現在キューに積まれている保存がすべて完了するのを待つ_waitForActivePageSaveQueue()を追加し、renderLayoutTab()がdbGet()する前にこれをawaitするようにした。

Kaptureで実機確認済み: レイヤーパネルの✕ボタンでオブジェクトを削除した直後、ページタブ→レイアウトタブへほぼ同時に(キューイングなしで)切り替える最も厳しいタイミングで再現テストを行い、削除したオブジェクトが復活しないこと・レイヤーパネルの行数が正しく減ったままであることを確認。新規コード起因のコンソールエラーなし。


2026-08-08(3Dテキストの複数行テキストが上下逆順に表示される不具合を修正、v1.31.1)

ユーザー報告「テキストボックスとプレビューが異なる(上下逆)」を受けて調査。⚙3D設定モーダルのテキストパネル(v1.31.0で新設した5行テキストエリア)に複数行入力すると、1行目がプレビューの下側、2行目以降が上側に表示されてしまう不具合があった。

原因: text3d-core.jsの_buildShapesForText()で、各行のベースラインY座標をopentype.jsのglyph.getPath(x, y, fontSize)へ渡す際、行番号iに対してy = -i * fontSizeWorld * lineHeight(iが増えるほど負)としていた。しかしopentype.jsのgetPath()はY軸下向き正の座標系で値を返すため、このファイルでは直後に全座標をshapePath.moveTo(cmd.x, -cmd.y)で符号反転してthree.jsの座標系(Y軸上向き正)に変換している(ファイル冒頭の既存コメント「注意1」参照)。この符号反転は渡したベースラインyにも等しく効くため、実際の最終Y座標は-yとなり、yが「iが増えるほど負」だと最終的には「iが増えるほど正(上)」になってしまい、2行目以降が1行目より上に表示される逆順になっていた。この不具合はロジック自体は3Dテキスト機能の初期実装から存在していたが、従来ツールバーのテキスト入力欄が1行(rows="1")だったため複数行入力される機会が少なく、v1.31.0で⚙3D設定モーダルに5行のテキストエリアを新設したことで顕在化した。

修正: y = -i * fontSizeWorld * lineHeight を y = i * fontSizeWorld * lineHeight に変更(符号反転を1つ減らし、後段の全体反転と合わせて「iが増えるほど最終Y座標が負(下)」という正しい向きにした)。

Kaptureで実機確認済み: テキストパネルに「text」→改行→「12345」と入力し、プレビューで「text」が上・「12345」が下(テキストエリアと同じ順)に表示されることを確認。新規コード起因のコンソールエラーなし。


2026-08-08(3Dテキストに SVG立体化・表面/側面カラー分離・設定モーダルのタブ化を追加、v1.31.0)

feature/text3d-svg-materialブランチで作業。参考アプリ3d-text-generator(React/three.js製プロトタイプ)の「SVGパス立体化」「表面色・側面色の個別指定」を、既存の3Dテキスト機能(レイアウトタブ・Imageタブ共有)へ移植し、あわせてユーザーからのフィードバックを受けて⚙設定モーダルをタブ構成に再設計した。

SVG立体化モード: 「テキスト」「SVG」のモード切替ボタンを追加。SVGモードではSVGファイルを選択すると、SVGLoader(three r160、comfyui-vrm-pose-editorが配信するthree.module.jsと同バージョンでvendor化)でパースしたベクターパスをTHREE.ExtrudeGeometryで押し出す(text3d-core.jsの_buildSvgGeometry()、参考アプリsvgToShapes.tsのロジックをvanilla js化して移植)。未パス化のテキスト要素(<text>)やビットマップ画像(<image>)を含むSVGは警告を表示する。

表面/側面カラー分離: THREE.ExtrudeGeometryが標準で持つ「表裏キャップ面=materialIndex 0」「側面=materialIndex 1」の2グループ(three.js本体の実装仕様)を利用し、new THREE.Mesh(geometry, [frontMaterial, sideMaterial])と配列を渡すだけで表面色・側面色を別マテリアルにした。テキスト・SVG両モード共通。面取り(厚み・サイズ・分割数)も詳細スライダーで調整できるようにした。

バグ修正1: 面取りの値によってSVGの3Dビューが真っ黒になる不具合。当初は「SVGのviewBoxサイズと押し出し座標系のスケール不一致」を疑ったが、実機調査(gl.readPixels()でキャンバスの実ピクセル値を直接確認)の結果、WebGL自体は正常に描画できており、原因は別にあると判明。⚙設定モーダルは実描画中のcanvasを一時的に自分のプレビュー枠へ「借りて」表示する仕組みだが、レイアウトタブ側の「コマの画面上位置にcanvasを追従させるResizeObserver」がモーダル表示中も動き続けており、モーダルが借りている最中のcanvasラッパーを、コマの本来の位置(画面外)へ強制的に書き戻してしまっていた(text3d-settings-modal.jsにonCloseコールバックを追加し、25-text3d-bridge.js側でモーダルを開く前にResizeObserverを止め、閉じたら再セットアップするよう修正)。あわせて、three.jsのExtrudeGeometry自体が面取りの自己交差で頂点座標や法線ベクトルを破綻させるケース(three.js本体の既知の制約)に対する防御コードも追加し、破綻を検知した場合は面取りを自動的に無効化して再構築し、無音で真っ黒になる代わりに警告メッセージを出すようにした(_isGeometryValid())。

⚙設定モーダルのタブ化(ユーザー要望): 3カラム構成に再設計。左=「ライト」「マテリアル」「カメラ」のタブ切替パネル、中央=実描画中のプレビュー、右=常時表示の「テキスト」パネル(テキスト内容・フォント・整列・行間スライダー(新規))。従来ツールバーに常時表示していたテキスト入力・フォント選択・整列ボタンをモーダルへ移動し、ツールバーはモード切替・押し出し厚み・面取りON/OFF・⚙3D設定ボタンのみのシンプルな構成にした。これに伴い、フォント読込ロジックをDOM値読取(fontSelect.value)からstate.text3d.fontFamily/fontSource(stateが正)駆動へリファクタリング(モーダルは開いている間しかDOM要素が存在しないため)。フォント一覧取得はレイアウトタブ・Imageタブで実装が異なる(Google一覧の取得元・システムフォントキャッシュが別々)ため、モーダル自体はロジックを持たずfontControlsコールバック一式を呼び出し側から受け取って委譲する設計にした。テキスト・整列・行間はモーダル内でeditorへ直接適用されるため、モーダルを閉じた時点(onClose)でeditor.getParams()から読み戻してstateに同期する。

UI文言調整(ユーザー要望): モーダルタイトルを多言語化しない固定名称「3D Text Editor」に変更(3Dポーズの「RC」ボタン表記と同じ扱い)。⚙ボタンを「⚙ 設定」→「⚙ 3D設定」に変更しツールチップに「テキスト編集」を追加。テキスト内容の初期値を空文字から「text」に変更し、「コマに配置」を押しただけで即座に3Dビューに何か表示され操作に迷わないようにした。

Kaptureで実機E2E確認済み: SVGファイル選択・立体化・表面/側面カラー分離・面取り詳細スライダー(意図的に極端な値を試し、以前は真っ黒になっていた組み合わせでも正しく表示され続けることを確認)・タブ切替・フォントソース切替(Google/システム)・整列・行間スライダーのライブ反映・モーダルを閉じた後の状態保持・UI文言変更を確認。新規コード起因のコンソールエラーなし。ヘルプ(ja/en/zh、22-help-tab.jsの_HELP_DATAと_HELP_I18N.en/.zh両方)・README 3言語を更新済み。依存先のcomfyui-vrm-pose-editor側もSVGLoader.jsのvendor追加をv0.12.0として先行リリース済み。v1.31.0としてリリース。


2026-08-07(デフォルトワークフロー系チェックボックスがRunに反映されないバグを修正、v1.30.1)

ユーザー報告「T2I一括生成でデフォルトワークフローのチェックを外して別のワークフロー(boogu_t2i.json)を生成UIタブに読み込んで実行したが、ernie_t2i.jsonで実行される」を調査。

原因: T2I/I2Iモーダルの「デフォルトワークフローを使用する」チェックボックス・ファイル名入力欄は、実行(Run)時には一切参照されず、「保存」ボタンを押してlocalStorageへ書き込んで初めて次回以降の実行に反映される設計になっていた。requestPanelImageFromWorkflowStudio()/sendI2IRunToWorkflowStudio()(14-integrations.js)が、呼び出し時の引数ではなく内部でモジュールスコープの_t2iSettings/_i2iSettings(保存済み設定)を直接参照していたため。チェックボックスを変更しただけで「保存」を押し忘れると、画面表示と実際の挙動が食い違う(今回は前回セッションでON+ernie_t2i.jsonを保存済みだったため、OFFにしても保存し直さない限りernie_t2i.jsonのまま実行され続けた)。

同じ設計上の欠陥が、スクリプトタブのT2I/I2Iモーダルだけでなく、レイアウトタブのI2Iモーダル(15-pixifx-bridge.js)とImageタブのSelect I2Iパネル(image-tab.js)の計4箇所すべてに存在していたため、まとめて修正した。

修正: requestPanelImageFromWorkflowStudio(prompt, width, height, negative, wfOverride)/sendI2IRunToWorkflowStudio(imageBlob, params, wfOverride)(14-integrations.js)に、任意のwfOverride: {enabled, file}引数を追加。渡された場合はそちらを優先し、省略時は従来通り保存済み設定(getT2ISettingsState()/getI2ISettingsState())を使う(既存呼び出し元との後方互換を維持)。4箇所すべてのRunボタンのクリックハンドラで、モーダル/パネルに今表示されているチェックボックス・ファイル名入力欄の値をそのままwfOverrideとして渡すよう変更。これにより「保存」ボタンは「次回モーダルを開いたときの初期値を保存する」という役割に純化され、Runは常に画面表示中の設定通りに実行されるようになった。

Kaptureで実機E2E確認済み: Comic Creator画面内のWorkflow Studioタブ経由でboogu_t2i.jsonを生成UIタブへ手動ロードし、T2Iモーダルで「デフォルトワークフローを使用する」をOFFにした状態(「保存」ボタンは押さず)でRunを実行。コンソールログで[Eagle] Saved: Boogu_00005_.png/Boogu_00006_.png(Booguモデル、boogu_t2i.json由来)を確認し、修正前に発生していた「ernie_t2i.jsonで実行される」問題が解消されたことを確認(test1作品、2コマとも成功)。検証後は「元に戻す」でtest1作品を挿入前の状態に復元済み。新規コード起因のコンソールエラーなし。v1.30.1としてリリース。


2026-08-07(「画像を一括生成」(T2I)をI2I同様のモーダルに変更、ヘルプ更新、v1.30.0)

ユーザーから「ernie_t2i.jsonをWorkflow Studioの生成UIタブに読み込んで実行したが、別のワークフローが実行されたようだ」との報告を受け調査。

原因調査: コードのバグではなく、ユーザーが手動でワークフローをロードしたのがComic Creator画面内に埋め込まれたWorkflow Studio(wfmgallery-iframe)ではなく、ブラウザの別タブとして独立に開いた/wfmだったことが原因と判明。Kaptureで両タブを確認したところ、Comic Creator内のiframe側は起動時に自動ロードされるデフォルトワークフロー(2.json)のままで、ユーザーが別タブでロードしたernie_t2i.jsonは反映されていなかった(同じURLでも別ブラウザタブ=別JS実行コンテキストのため状態が共有されない)。「画像を一括生成」(requestPanelImageFromWorkflowStudio)は「専用ワークフロー選択には未対応で常にGenerate UIに現在ロード中のワークフローをそのまま使う」設計だったため、iframe側の2.jsonがそのまま実行されていた。

対応(ユーザー要望):

  • T2I版「画像を一括生成」のモーダル化: 既存の「画像を一括生成(I2I)」と同じ構成(全体Positive/Negativeプロンプト、「コマの画像プロンプトが空の場合はスルーする」チェック、デフォルトワークフロー指定)を持つ専用モーダルに変更。_handleAutoImageGenerateClickを_runAutoImageGenerateT2I+_openAutoComicT2IModalに置き換え(26-auto-comic-bridge.js)。I2I/Nanobananaモーダルと共有していたPositive結合ヘルパー_composeI2IPositiveを_composeOverallPromptへ汎用リネーム。
  • 14-integrations.jsに、I2I/Inpaint設定と同じ独立パターンで_t2iSettings(localStorage ccc_t2i_settings)・getT2ISettingsState/saveT2ISettingsStateを新設(T2I/I2I/Inpaintそれぞれ別々のデフォルトワークフローを持てる)。requestPanelImageFromWorkflowStudio(prompt, width, height, negative)を拡張し、T2I設定が有効な場合は実行前にワークフローJSONを取得してWorkflow Studio側へ渡すようにした。
  • Workflow Studio側gallery-tab.jsの_wfmReceiveGenerateRequestをI2I/Inpaintと同じパターンに拡張(negative・workflowData・workflowFilename引数を追加、詳細はComfyUI-Workflow-Studio側DEVLOG参照)。
  • モーダル下部の見出しをI2Iと共通の「I2I設定」のまま流用していたところ紛らわしかったため、専用の「T2I設定」ラベル(script.autoT2ISettingsHeading)を追加。
  • ヘルプ更新(ja/en/zh): 「画像を一括生成」の説明をモーダル仕様に更新。「Workflow Studio連携について」の独立<li>を新設し、L/T2I/I2I連携機能が対象とするのはComic Creator画面内の「Workflow Studio」タブに埋め込まれたインスタンスであり、ブラウザの別タブ・別ウィンドウで開いたWorkflow Studioとは無関係である点を明記(今回の調査結果を踏まえた注意喚起)。「画像を一括生成(I2I)」の説明にDenoise指定は対応ワークフロー(denoiseパラメータを持つノードを含むもの)のみ有効という注記を追加。

Kaptureで実機E2E確認済み: T2Iモーダルの表示・入力保持(Positive/Negative/スルーチェック/デフォルトWF設定)を確認。ccc_t2i_settingsがccc_i2i_settings/ccc_inpaint_settingsとは独立してlocalStorageに保存されることを確認。実際にComic Creator画面内のWorkflow Studioタブ経由でernie_t2i.jsonをT2I設定のデフォルトワークフローに指定してRunを実行し、/queue監視・コンソールログでernie_t2i.jsonのモデル(Ernie-Image-Turbo)による生成が実行されEagleへ保存されたことを確認(test1作品、2コマとも成功、レイアウトタブへ自動挿入)。検証後は「元に戻す」でtest1作品を挿入前の状態に復元済み。新規コード起因のコンソールエラーなし。ユーザー指示によりボタン名を「画像を一括生成」→「画像を一括生成(T2I)」に変更(i18n・ヘルプ・README 3言語とも追従)。v1.30.0としてリリース。


2026-08-05(半自動マンガ「画像を一括生成(Nanobanana)」機能を追加、v1.29.0)

feature/semi-auto-comic-nanobananaブランチで作業。スクリプトタブの半自動マンガ作成機能に、既存の「画像を一括生成(I2I)」(Workflow Studio連携)と対になる、Nanobanana(Gemini画像生成API、CC自身が持つ独立タブnanobanana.jsと同じ連携)を使うバッチI2I生成機能を追加した。まずは既存I2Iモーダルとは別のボタン・別のモーダルとして実装し、バックエンド選択式の1モーダルへの統合は将来の検討課題とする。

  • auto-comic-core.jsに、既存pickSdxlResolutionと同じ対数スケール比較ロジックのpickNanobananaResolution(aspectRatio)を追加。Nanobananaの解像度プリセット(5種類、値はSDXLと現状同一)から、コマのbboxアスペクト比に最も近いものを自動選択する。
  • nanobanana.jsのNanobananaManager内に埋め込まれていた「APIキー確認」「生成API呼び出し」「base64正規化+サーバー保存+Eagle自動保存」の3ロジックを、モジュールスコープの独立関数(checkNanobananaKeyStatus/requestNanobananaGenerate/saveNanobananaImageAndMaybeEagle)として切り出しexport。既存Nanobananaタブ自体もこれらを呼ぶ形にリファクタリングし、挙動を変えずに半自動マンガ側と共有できるようにした。
  • 26-auto-comic-bridge.jsに「画像を一括生成(Nanobanana)」ボタン・モーダル(_openAutoComicNanobananaModal)・実行ロジック(_runAutoImageGenerateNanobanana)を追加。既存I2Iモーダルと同じ対象コマ絞り込み・Positive結合・pushHistoryの設計を踏襲しつつ、モデル選択(3種)・I2I強度スライダー・接続状態表示を持つ。各コマの現在の画像を_getPanelImageBlobでラスタライズしdata URL化してNanobanana APIへ送信し、結果はコマのbboxへpreserveAspectRatio:'none'でストレッチ挿入する(T2I/I2Iバッチと同じスケール調整方式)。
  • 判明した既存事実: py/ccc.pyのhandle_nanobanana_generateはwidth/heightを実ピクセル指定ではなくアスペクト比文字列に変換してGeminiへ渡すのみで、strengthパラメータは一切処理していない。既存Nanobananaタブのstrengthスライダーも現状バックエンド未反映であり、新モーダルのstrengthスライダーも同じ制約を引き継ぐ(バックエンド改修は今回スコープ外)。
  • Kaptureで実機E2E確認済み: 既存Nanobananaタブの回帰なし(リファクタリング後も接続状態表示・生成が正常動作)、新モーダルのUI表示・入力項目がすべて正常、test1作品(半自動マンガ、2コマ)で実際にRunを実行し2コマとも生成成功→レイアウトタブへ自動切替→コマ全面へ隙間なくストレッチ挿入を確認。ネットワークログで/api/ccc/nanobanana/generate→/api/ccc/save-nanobanana-image→/api/ccc/eagle/addの呼び出し順序を確認(Eagle未起動環境のためeagle/addは500だったが、fire-and-forgetのため他コマの処理には影響せず両コマとも成功)。挿入された<image>のhrefがdata:image/...;base64,...形式であること、および作品保存→リロード→再読込後も画像が壊れずに表示されることを確認(過去のblob URL永続化バグの再発なし)。新規コード起因のコンソールエラーなし。
  • ヘルプ(ja/en/zh、「プロット」節に新規<li>)・README(3言語)を更新済み。バージョンアップ・mainへのマージ・プッシュは別途指示があるまで行わない。
  • 追加修正: I2I強度スライダーを削除、「2K」生成トグルを追加(Nanobananaタブ・半自動マンガモーダル両方)。 ユーザー指示によりGemini画像生成APIの公式ドキュメント(https://ai.google.dev/gemini-api/docs/image-generation・https://ai.google.dev/api/generate-content)を確認した結果、(1) I2Iの変化度合いを数値で指定するパラメータ(denoising strength等)はAPIに一切存在せず、変化の強さはPositiveプロンプトの文章のみで制御する仕様であることが確定した。(2) generateContentエンドポイントのGenerationConfig.imageConfig(ImageConfig型)に、既存実装が使っているaspectRatioに加えてimageSizeフィールド("1K"/"2K"/"4K"、未指定時は"1K")が存在することが判明した(モデルにより対応状況が異なり、gemini-3.1-flash-lite-imageは1Kのみ対応、gemini-3.1-flash-image/gemini-3-pro-imageは2K/4Kまで対応)。これを受けて、①nanobanana.js・26-auto-comic-bridge.js・templates/index.htmlからnanobanana-i2i-strengthスライダー(UIのみでバックエンド未反映だった)を削除、②py/ccc.pyのhandle_nanobanana_generateにimage_sizeリクエストパラメータを追加しimageConfig.imageSizeへ渡すよう変更、③両UIの解像度セレクト隣に「2K」チェックボックスを追加(選択したアスペクト比を維持したまま約2倍相当の解像度で生成、対応モデル選択時のみ有効)。i18nキーnb.i2iStrengthをnb.2kLabel/nb.2kHintに置き換え(3言語)。Kaptureで確認したところ、JS側(UIの表示・strength削除・リクエストペイロードへのimage_size付与)は正常動作を確認したが、py/ccc.pyの変更はComfyUIのカスタムノードロード方式上サーバー再起動が必要だった(ユーザーが再起動を実施)。再起動後、Nanobananaタブでgemini-3.1-flash-imageモデル+「2K」ONで生成→naturalWidth/naturalHeightが2048×2048(既定の1024×1024から狙い通り2倍)になることを確認。gemini-3.1-flash-lite-image(2K非対応)では従来通り1024×1024のまま生成されることも確認済み。半自動マンガのNanobananaモーダルでも同モデル+2K ONでtest1作品の2コマに対しRunを実行し、2件とも生成成功→コマのbboxへスケール調整挿入(挿入された<image>のdata URL長が従来の1K生成時(26万〜100万文字程度)に対し2K生成時は245万〜360万文字程度と明確に大きく、高解像度化を確認)。両モーダルとも新規コード起因のコンソールエラーなし。

2026-08-05(Imageタブヘルプの翻訳漏れ修正・レイアウトタブ表示サイズの不連続ジャンプ修正・アセットパネル「P」タブ更新漏れ修正、v1.28.0)

ユーザー報告の3件に対応。いずれもバグ修正で、未リリースの下記「3Dポーズタブに風エフェクト」(2026-08-04)と合わせてv1.28.0としてまとめてリリースする。

1. Imageタブヘルプの翻訳漏れ修正: ユーザーが以前のセッションで見つけていた「Imageタブ『アクションバー: 開始/終了』見出しの『下書き』ボタンの説明が英語・中国語版に欠けている」を確認・修正。22-help-tab.jsの日本語版(163行目)にはUpload/New/下書き/Closeの4項目があったが、英語版(Action Bar: Start/End)・中国語版(操作栏:开始/结束)にはUpload/New/Closeの3項目しかなく、下書きボタンの説明が丸ごと欠落していた。また関連するもう1箇所、「アクションバー: 保存系ボタンの違い」の「レイアウトに送る」説明末尾にある「ただし『下書き』ボタンで作成したキャンバスは例外で、常に下書きレイヤーへページ全面サイズで挿入される」という一文も英語・中国語版に反映されていなかった。日本語版の内容に合わせて両言語とも追記し、3言語を揃えた。

2. レイアウトタブ「表示サイズ」スライダーの不連続ジャンプ修正: 「表示100%から105%に変更すると数値とかけ離れた大きさになる」という報告を受け調査。02-assets.jsの_applyLayoutPreviewSize(pct)が、pct <= 100のときだけmax-height:80vhを適用し、101%以上では丸ごと解除する実装になっていたのが原因。100%ちょうどの時点では高さ制限によりSVGの実効表示幅が縮小されていたため、101%になった瞬間に制限が消えて「親コンテナ幅のpct%」がそのまま適用され、Kaptureでの実測でも100%→105%で幅572px→822px相当(本来なら601px、5%増のはず)まで跳ね上がることを確認した。修正としてSVGのviewBoxから求めた本来の縦横比をもとに、「コンテナ幅」と「高さ80vh」のうち小さい方を100%の基準サイズとし、pctをその基準への倍率として一貫適用するようCSSのmin()/calc()で書き換えた(max-heightの分岐は撤廃し常にnoneに)。線形性により、これは実質「pctによらず常にどちらか一方の制約だけが効く」計算と数学的に同一になるため、ウィンドウリサイズにも追従する。Kaptureで100%→105%(572.57→601.20px、809.77→850.26px、いずれも正確に×1.05)、25%(正常縮小)、300%(正常拡大・横スクロールバー機能)を実機確認済み。新規コードに起因するコンソールエラーなし。

3. アセットパネル「P」タブの更新漏れ修正: 「レイアウトタブでPタブ(ページサムネイル一覧)を表示している状態のまま他タブへ移動し、そちらでページ関連の操作(保存・並び替え・リネーム・削除・作品切替等)をしてからレイアウトタブに戻ると、Pタブの中身が移動前のまま更新されず違和感がある」という報告を受け調査。01-state.jsのswitchTab()は、tabId === 'layout'で戻ってきた際にrenderLayoutTab()(メインキャンバス)とrenderAssetFontGrid()のみを再描画しており、renderPageThumbGrid()(Pタブの中身)は呼んでいなかったことが原因。他タブでの変更自体は07-pages.jsのrenderPageSelector()経由でstate.pagesやDOM(#page-thumb-grid)には正しく反映されるが、レイアウトタブに戻るだけではその再描画がトリガーされない設計だった。switchTab()のレイアウトタブ復帰処理に、アセットパネルの現在のアクティブタブがpagesの場合のみrenderPageThumbGrid()を呼ぶ分岐を追加。


2026-08-04(3Dポーズタブに風エフェクト(そよ風・発生源マーカー)の操作ボタンを追加、v1.28.0)

comfyui-vrm-pose-editor側(開発元: vrmpose_light_plus_2\3dpose_light_editor)に新規実装された「風エフェクト」機能へ、Comic Creator側のUIを追従させた。ユーザーからの「Workflow Studioのレイアウトタブの3Dポーズ機能の更新も必要か」という質問を受けて調査したところ、Workflow Studio自体には3Dポーズ機能が無く、実体はComic Creator側のレイアウトタブ「3Dポーズ」サブタブであることが判明。そちらへの追加対応を行った。

背景(comfyui-vrm-pose-editor側の新機能): pose_editor_core.jsに、VRMの揺れボーン(髪・スカート等)へthree-vrmのgravityDir/gravityPowerを毎フレーム上書きする方式でsin波合成のそよ風を加える機能(_windEnabled等、editor APIとしてgetWindEnabled()/toggleWindEnabled()/getWindStrength()/setWindStrength()/getWindDirection()/setWindDirection()/getWindTurbulence()/setWindTurbulence())と、視線(LookAt)機能と同じ仕組みでドラッグ可能な3Dコーンから風向きを指定できる「発生源マーカー」機能(getWindSourceEnabled()/toggleWindSourceEnabled())が追加された。

Comic Creator側の対応: Comic Creatorはpose_editor_core.js・light_editor.js・pose_library.jsのみを/extensions/comfyui-vrm-pose-editor/経由の動的importで参照しており(pose_editor_3d.jsのツールバーDOMには依存していない)、独自のツールバーHTML(templates/index.html、pose3d-lookat-btn等)とイベントハンドラ(static/js/main/23-pose3d-bridge.js)からeditor APIを個別に呼び出す設計になっている。そのため今回のAPI追加だけではUI操作ができず、過去の視線・揺れ物理トグル追加時と同様、対となるUIボタンを手動で追加する必要があった。

  • templates/index.html: pose3d-springbone-btn(🎐揺れ)の直後にpose3d-wind-btn(🌬 風)・pose3d-windsource-btn(🧭 発生源)ボタンを追加。
  • static/js/main/23-pose3d-bridge.js: initPose3DTab()内でDOM取得を追加し、既存のlookAtBtn/springBoneBtnと同じパターン(editor.toggleXxx()→ボタンのtextContent・背景色を更新)でイベントハンドラを追加。
  • static/js/i18n.js: layout.pose3dWindTitle/layout.pose3dWindSourceTitleを日本語・英語・中国語の3言語で追加。
  • 強さ・向き・そよぎの詳細パラメータ調整、発生源マーカーONの間の「向き」スライダー無効化は、既存のpose3d-light-btn(ライトエディタ)経由でそのまま利用可能(light_editor.js自体を動的importしているだけのため、Comic Creator側の追加実装なしで自動的に反映される)。

検証: Kaptureで実機確認済み。レイアウトタブ→3Dポーズサブタブ→コマに配置後、「風」ボタンでeditor.toggleWindEnabled()が呼ばれボタンが青くハイライトすること、「発生源」ボタンでeditor.toggleWindSourceEnabled()が呼ばれ3Dビュー(pose3d-canvas)内にオレンジのコーンマーカーが実際に表示されることを確認。新規コードに起因するコンソールエラーは発生しなかった。なお、このタブでは「default model」の自動ロードがComic Creator側の既存の仕様(baseUrl解決)により404になる(今回の変更とは無関係の既存の挙動)ため、実VRMモデルでの揺れ確認はできていない。

ドキュメント: ヘルプ(22-help-tab.js、日英中3言語)の「3Dポーズサブタブ」節に「🌬 風」「🧭 発生源」の説明を追加。あわせて、日本語版にのみ存在し英語・中国語版では抜けていた「👁 視線」「🎐 揺れ」の説明も今回3言語で揃えた。README(3言語)の「3D ポーズ」機能概要にも風エフェクトの記載を追加済み。リリースはユーザー判断で後日行う予定。

重大な不具合と修正: 上記ヘルプ更新の直後、ユーザーから「ページタブの作品管理で作品一覧が表示されない。新規作成・テンプレートタブ・レイアウトタブも開かない」という報告があり調査した。原因は22-help-tab.jsの英語版ヘルプ本文(3Dポーズサブタブ節、今回追加した「👁 LookAt」「🎐 Sway」「🌬 Wind」の説明文)に含まれるmodel's/marker'sという所有格のアポストロフィがエスケープされずに生のままJS文字列リテラル('...')内に混入していたこと。文字列がそこで終端したとブラウザのパーサーに誤認識され、SyntaxError: Unexpected identifier 's'でモジュール評価そのものが失敗し、ヘルプタブどころか作品管理・テンプレート・レイアウトタブまで巻き込んで全機能が使用不能になっていた。node --checkではなぜかこの構文エラーが検出されず(トークン列としてたまたま成立してしまっていたためと推測)、実際にブラウザで評価して初めて顕在化する類のバグだった。

  • 原因特定: git stashで今回の全変更を一時退避してエラーが消えることを確認したのち、git checkout stash@{0} -- <file>で1ファイルずつ復元しながらKaptureでブラウザの再現確認を繰り返す二分探索で22-help-tab.jsに絞り込んだ。ファイルサイズ・ネットワークレスポンスのバイト完全一致も確認し、文字化けやキャッシュ起因ではないことを先に排除した。
  • 修正: 該当3箇所のmodel's/marker'sをmodel\'s/marker\'sにエスケープ。中国語版・日本語版には所有格アポストロフィ表現自体が存在しないため、他言語への修正は不要だった。
  • 教訓: JS文字列リテラルへ長文の英語説明文を追加する際は、node --checkが通っても実ブラウザでの構文的な健全性の担保にはならない場合がある。所有格・短縮形のアポストロフィ('s、doesn't等)を含む英文をシングルクォート文字列へ追加するときは特に注意する。
  • 検証: 修正後、node --check・実ブラウザともにエラーが解消し、Kaptureで「作品管理」の作品一覧・「テンプレート」タブのサムネイル一覧・「レイアウト」タブ(ツールペイン・レイヤーパネル含む)がすべて正常に表示・動作することを確認した。3Dポーズサブタブの🌬/🧭ボタンも引き続き正しく表示されることを確認済み。

2026-08-04(プロットに「フキダシ形状」列を追加、セリフごとに自動生成の形状を指定可能に、v1.27.0)

ユーザー依頼「プロットにフキダシ形状指定の列を追加したい。セリフごとにドロップダウンで選択可能とし、フキダシを自動生成ボタンで指定のフキダシ形状で作成したい。指定がない場合は現在の矩形で作成としたい。」に対応。Phase 1設計時点で「今回は見送り」としていた項目(feature-semi-auto-manga-creation参照)を実装した。

データ構造: 21a-script-manga.jsのセリフオブジェクトにshapeフィールドを追加({character, text, shape})。値は空文字(未指定=自動生成時は角丸矩形にフォールバック)またはnormal/rect/thought/bomb/cloudpuffy/cloudwavy(09c-balloon-handles.jsのh2-shape-typeセレクトと同じ6値)。_scriptMangaBlankDialogue()・_scriptMangaNormalize()を更新し、旧形式データ(shapeフィールドなし)は読込時に自動的に空文字(既定=角丸矩形)へ補完される。

UI: プロットのコマワリテーブルに列を追加(シーン|コマ番|画像プロンプト|セリフ番|要素|セリフ/説明等|フキダシ形状の7列)。各セリフ行に<select>(既定/通常/角丸矩形/思考/バクダン/雲もこもこ/雲なみなみ)を配置。選択肢の文言は既存のレイアウトタブ「フキダシ」ツールのh2-shape-typeセレクトと同じi18nキー(layout.balloonNormal等)をそのまま再利用したため、新規翻訳は列見出しと既定オプションのみで済んだ。

生成ロジック: 26-auto-comic-bridge.jsの_handleAutoBalloonGenerateClick()で、これまで固定値AUTO_BALLOON_TYPE('rect')を渡していた箇所をdialogues[i].shape || AUTO_BALLOON_TYPEに変更。createBalloonAtPosition()/_buildBalloonShapeEl()(09c-balloon-handles.js)は既に全6形状のデフォルトパラメータ(バクダンのスパイク数、思考の泡サイズ、角丸矩形の角丸半径等)に対応済みだったため、呼び出し側の変更のみで完結した。

検証: Kaptureで実機E2E確認済み。既存作品「これはTESTです。」(漫画メディア、旧形式データ)を読み込み、フキダシ形状列が「既定(角丸矩形)」で正しく初期表示されることを確認(後方互換)。1行目を「バクダン」に変更しリロード後も保持されることを確認。「test1」(半自動マンガ)でコマ1の2セリフをそれぞれ「雲もこもこ」「思考(泡)」に設定し「フキダシを自動生成」を実行、指定通りの形状で生成されレイアウトタブに反映されることをスクリーンショットで確認(コマ2は未指定のため角丸矩形で生成)。新規コードに起因するコンソールエラーなし。

ドキュメント: ヘルプ(22-help-tab.js、日英中3言語、プロット列一覧・フキダシ自動生成の説明を更新)・README(3言語)を更新済み。


2026-08-04(スクリプトタブ: 「コマ数取得」実行時に誤削除防止の確認ダイアログを追加、v1.26.1)

ユーザー依頼「コマ数取得実行時確認メッセージを出したい。誤ってデータのあるコマを消してしまう恐れがあるため。」に対応。

21a-script-manga.jsに_scriptMangaPanelHasData(panel)(コマの画像プロンプトまたはセリフのいずれかに非空の値があるか判定)を追加。「コマ数取得」ボタンのハンドラで、コマ数が減る場合に削除対象のコマ(末尾からtargetCount超過分)へこの判定を適用し、1つでもデータ入りのコマが含まれていればconfirm(t('script.panelCountSyncConfirmDataLoss', 現在数, 変更後数))で確認する。キャンセル時は何も変更しない。空のコマのみが削除される場合は従来通り確認なしで即実行(既存の▲▼ボタンと同じ利便性を維持)。既存の作品名・削除確認等と同じconfirm(t('script.confirmXxx'))パターンを踏襲。

Kaptureで実機E2E確認済み: コマ3に画像プロンプトを入力した状態で5→2への「コマ数取得」を実行し、確認ダイアログが表示されることを確認。キャンセル時はコマ数・入力内容とも変更されないこと、OK時はコマ数が正しく2に減り残り2コマのデータは保持されることを確認。空コマのみ削除されるケース(データなし)では確認なしで即実行されることも既存動作から確認済み。

ドキュメント: ヘルプ(22-help-tab.js、日英中3言語、プロット節の「コマ数取得」説明に確認ダイアログの挙動を追記)を更新済み。


2026-08-04(スクリプトタブ: 「コマ数取得」ボタン追加+作品名/メディア種別/あらすじのレイアウト整理、v1.26.0)

ユーザー依頼「スクリプトタブ、メディア種別:半自動マンガ、マンガのコマ数をレイアウトタブの選択中のページのコマ数から取得するボタンを追加したい。また、作品名:、メディア種別:を1行に、あらすじを要素タブ、右隣にしてプロット入力作業スペースを確保したい。」に対応。

「コマ数取得」ボタン: プロットのツールバー、コマ数▲▼の右に追加(21a-script-manga.js)。クリックするとstate.activePage?.panels?.length(レイアウトタブで現在選択中のページの実際のコマ数)を取得し、表示中スクリプトページのコマ数をそれに合わせる(不足分は_scriptMangaBlankPanel()を末尾へpush、超過分はpop。既存の▲▼ボタンと同じ増減ロジックを再利用)。レイアウトタブでページが未選択の場合はエラーメッセージを表示。既存の21-script-tab.jsがstate(01-state.js)を直接importしていなかったため新規importを追加したが、01-state.js → 21-script-tab.js → 21a-script-manga.js → 01-state.jsという循環参照になる。ただしstateの参照はクリックハンドラ内部(呼び出し時点で評価)に閉じているため、既存の01-state.js ⇄ 26-auto-comic-bridge.js循環と同じパターンで安全(comic-creator-workflow参照)。「漫画」「半自動マンガ」どちらのメディアでも使用可能(既存の流し込み等と同じ扱い)。

レイアウト整理(プロット編集エリアの縦スペース確保): templates/index.htmlを編集。①作品名・メディア種別を別々の.project-plot-sectionから1つの.project-plot-section-header内へ統合し1行表示に。②あらすじ入力欄を、常時表示だった位置(サブタブ切替前、あらすじ4行分の固定高さを常に占有していた)から「要素」サブタブへ移設し、要素テーブル(左)とあらすじ(右、.script-synopsis-col、幅340px・flex:1で残り高さいっぱいに伸びる)の2カラムレイアウトに変更。style.cssに.script-elements-layout/.script-elements-col/.script-synopsis-colを追加。#script-synopsis/#script-elements-tbody等の関連JSはすべてID直指定のみで親要素の構造に依存していなかったため、JS側の変更は不要だった。

検証: Kaptureで実機E2E確認済み。「test1」(半自動マンガ、2コマ)読込→要素サブタブであらすじが右カラムに正しく表示・作品名とメディア種別が1行表示されることを確認。コマ数を5に増やした状態で「コマ数取得」を実行し、レイアウトタブの選択中ページ(2コマ)に正しく戻ることを確認(既存セリフ・画像プロンプトも先頭2コマ分は保持)。レイアウトタブでページ未選択時のエラーメッセージ表示も確認。新規コードに起因するコンソールエラーなし。

ドキュメント: ヘルプ(22-help-tab.js、日英中3言語、「作品名・あらすじ」→「作品名・メディア種別」に見出し変更、プロット節・要素節を更新)・README(3言語)を更新済み。


2026-08-04(半自動マンガ作成: 一括生成(T2I/I2I)の画像がコマより小さくなる不具合を修正、v1.25.1)

ユーザー報告「生成した画像のサイズがコマよりも小さくなる」を調査し、2つの原因を特定・修正した。いずれも26-auto-comic-bridge.jsが原因。

原因1(T2I/I2I共通): auto-comic-core.jsのpickSdxlResolution()はSDXL標準解像度5段階(アスペクト比0.57〜1.75)しか選択肢がなく、コマの実際のbboxアスペクト比がこの範囲から外れるほどズレが大きくなる。08-panels-images.jsのinsertImage()はplacement未指定時「幅をコマ幅に合わせ、高さは画像のアスペクト比のまま」計算するため(アスペクト比を保つcontainフィット)、生成画像がコマより相対的に扁平だと高さがコマの高さに届かず、コマ内に空白帯が残っていた。

原因2(I2Iのみ、より深刻): 新設した_getPanelImageBlob()がコマのbboxをviewBoxに設定したSVGをdrawSvgOnCanvas()でラスタライズする際、preserveAspectRatioを指定していなかったため、デフォルトのxMidYMid meet(アスペクト比保持の中央フィット)が適用され、I2Iへ送信する時点で既にコマの絵が縮小され周囲に白余白ができた状態になっていた。I2Iはこの構図をそのまま踏襲するため、結果画像も同様に小さくなりやすかった。

修正: ①_getPanelImageBlob()のSVGへpreserveAspectRatio="none"を追加し、キャプチャ時点のレターボックスを解消。②T2I/I2Iとも、insertImage()呼び出し時にコマのbboxを明示的なplacementとして渡し、生成画像をコマへストレッチ挿入するよう変更(既存のレイアウトタブ「ページ全体I2I」と同じ設計)。③insertImage()の<image>要素自体もplacement指定時にpreserveAspectRatio未指定だと箱の中でさらにアスペクト比保持フィットされてしまうため、extraAttrs経由でpreserveAspectRatio: 'none'を渡し箱全面へ伸縮させるようにした(insertImage()本体は変更せず、既存のextraAttrs→setAttributeの仕組みをそのまま利用)。

検証: Kaptureで実機E2E確認済み。手書きイラスト2コマページに対しT2I・I2Iとも再実行し、両コマとも画像がコマ全面を隙間なく覆う(白余白なし)ことをスクリーンショットで確認。新規コードに起因するコンソールエラーは発生しなかった。


2026-08-04(半自動マンガ作成 Phase 3拡張: 「画像を一括生成(I2I)」を追加、v1.25.0)

T2Iのみだった「画像を一括生成」に加え、コマの現在の画像を入力にWorkflow Studio経由でI2Iバッチ生成できる「画像を一括生成(I2I)」ボタンを追加した。レイアウトタブの既存I2Iモーダル(15-pixifx-bridge.js openLayoutI2IModal)をベースにしたスクリプトタブ専用モーダルを新設し、26-auto-comic-bridge.jsに実装。

仕様: 既存I2Iモーダルと異なり対象選択(選択画像/ページ全体)は無く、常に対応付け済みの全コマが対象。モーダルのPositiveプロンプト(全体指示)はコマごとの画像プロンプトと結合して送信され(_composeI2IPositive()、どちらか一方が空でも成立)、Negativeプロンプト(全体)・Denoiseも指定できる。「コマの画像プロンプトが空の場合はスルーする」チェックボックスで、T2Iと同じ絞り込み(画像プロンプトが空のコマをスキップ)と、チェックOFF時の「全コマ対象・モーダルの全体指示だけでも処理される」動作を切り替えられる。I2I設定(デフォルトワークフローの使用・ファイル名)はレイアウトタブのI2Iモーダル・ImageタブのSelect I2Iパネルと同じ_i2iSettings(14-integrations.js)を共有し、モーダル内に同じUIを追加した。

実装: 各コマの現在の画像(panel.panelSvgContent)をコマのbbox範囲だけラスタライズしてBlob化する新規ヘルパー_getPanelImageBlob()を追加。15-pixifx-bridge.jsの_getPageBlob(ページ全体対象)と同じ手法(embedFontsInSvg→drawSvgOnCanvas→canvas.toBlob)を使い、panelSvgContentのルートsvgのviewBoxをコマのbboxへ差し替えるだけで、そのコマの領域だけがクロップして描画される(panelSvgContentがページと同じ座標系で描かれているため)。生成リクエストは既存のレイアウトタブI2Iモーダルと同じsendI2IRunToWorkflowStudio()をそのまま再利用したため、Workflow Studio側の変更は一切不要だった。結果画像は前回修正した_urlToDataUrl()(blob: URL→base64データURL変換)を経由してからinsertImage()で挿入するため、保存→再読込後もリンク切れにならない。既存画像は削除せず新しい<image>として追加する仕様(T2Iバッチと同じ、ユーザー承認済み)。

検証: Kaptureで実機E2E確認済み。手書きイラストが入った2コマページに対し、Positive「masterpiece, high quality, anime style, clean lineart」・Negative「low quality, blurry」・Denoise 0.5・スルーOFFで実行し、両コマの画像がアニメ調タッチへ変換されてレイアウトタブへ自動切替されることを確認(hrefはいずれもdata:image/png;base64,...形式で永続化を確認)。I2I設定セクション(デフォルトワークフロー使用・ファイル名の共有読み込み・保存)も動作確認済み。新規コードに起因するコンソールエラーは発生しなかった。

検証中に判明した既知の注意点(新規バグではない): ページをリロードするとstate.activePage(レイアウトタブのアクティブページ)がクリアされ、ページ管理タブでページカードを再度開き直す必要がある(テンプレート未選択エラーの原因になりやすい)。今回のHTML変更(新規ボタン追加)はページの完全リロードが必要だったため、検証中に踏んだ。

ドキュメント: ヘルプ(22-help-tab.js、日英中3言語、「プロット」節に新規<li>追加)・README(3言語)を更新済み。


2026-08-04(半自動マンガ作成 Phase 3: 一括生成画像がリロード後に壊れるバグを修正、v1.24.1)

Phase 3の「画像を一括生成」で挿入した画像が、作品を保存して閉じ、再度開くとリンク切れになる不具合を修正した。

原因: 26-auto-comic-bridge.jsの_handleAutoImageGenerateClick()が、Workflow Studio側から返る生成結果URL(genResult.url、_wfmReceiveGenerateRequestがdocument.getElementById("wfm-gen-result-img")?.srcから読む値)を変換せずそのままinsertImage()へ渡していた。Workflow Studio側generate-tab.jsの通常のPNG/JPEG生成結果はURL.createObjectURL(blob)で作ったblob:URLであり、ページ(ドキュメント)のセッション限りでしか有効でない一時参照。insertImage()は渡された文字列をそのままSVGの<image href="...">へ埋め込んでpanelSvgContentとしてIndexedDBへ永続化するため、blob:URLがそのまま保存され、次回ページ読込時(=blob URLを発行した元ドキュメントが破棄された後)には解決不能になり画像が壊れて見える。

修正: 既存のinsertImageFromUrl()(08-panels-images.js)が使っている「fetch→blob→FileReader.readAsDataURL()でbase64データURL化」という変換パターンを踏襲し、_urlToDataUrl(url)ヘルパーを26-auto-comic-bridge.jsに追加。insertImage()へ渡す前にgenResult.urlをこれで変換することで、常に永続化可能なbase64データURLがSVGへ埋め込まれるようにした(fetch()はblob:URLにも通常のURLにも使えるため、Workflow Studio側の戻り値の種類(PNG/JPEGはblob:、SVG出力は/wfm/gallery/image/serve?path=...という別オリジン依存の相対URL)に関わらず統一的に対応できる)。

Why: comic-creator-workflowの「画像挿入は永続化可能な形式に変換してから行う」という既存の設計原則(insertImage()のパラメータ名base64Dataが示す契約)が、Phase 3の新規コードパスでは守られていなかった。今後、外部(Workflow Studio等)から受け取った画像URLをinsertImage()系関数に渡す新規コードを書く際は、必ずinsertImageFromUrl()と同じfetch→blob→dataURL変換を経由すること。

検証: node --checkで構文確認(vm.SourceTextModuleは今回の変更が単純な関数追加のみのため省略)。ロジックの妥当性は既存のinsertImageFromUrl()と同一パターンであることで担保。


2026-08-03(半自動マンガ作成 Phase 3: Workflow Studio連携によるLLMプロンプト下書き+コマ単位バッチ画像生成を追加、v1.24.0)

Phase 1(対応付け基盤)・Phase 2(フキダシ自動生成)に続き、3フェーズ計画の最終フェーズを実装した。別リポジトリComfyUI-Workflow-Studio(C:\Users\statsu-11\Desktop\now_work\comfyUI-wf-maneger\ComfyUI-Workflow-Studio)側にも変更が及ぶ機能追加。

Workflow Studio側の調査・実装: ai-tab.jsの翻訳タブが既に持つLLM接続ロジック(callLLM(url, backend, model, prompt)、loadAiSettings()=localStorage wfm_ai_settingsからOllama/LM Studio設定を読む、isValidBackendUrl())をexportに追加してgallery-tab.jsから再利用できるようにした(新規実装は最小限)。画像生成については、AIチャットタブのgenerateImageFromChatが既にwindow._wfmGenerateTab.generate(workflow)(Generate UIの実行エントリポイント、generate-tab.jsで公開済み)を使っていたため、これをそのまま踏襲。解像度指定はcomfyui-editor.jsの設定タブ「Latent Image」パネルがanalysis.latent_nodes[0](EmptyLatentImage/EmptySD3LatentImageノードを検出、{id, width, height}を返す)を使ってworkflow[id].inputs.width/heightを直接書き換えているのと同じ手法を採用した。

gallery-tab.jsに、既存の_wfmReceiveImageForI2I等と同じ配置パターンで新規ブリッジ2つを追加: _wfmReceiveLLMPromptRequest(context)(シーン・要素・セリフ・現在のプロンプトから画像生成プロンプト案をLLMで下書き)、_wfmReceiveGenerateRequest(prompt, width, height)(現在ロード中のワークフローでtxt2img生成を実行、document.getElementById("wfm-gen-result-img")?.srcから結果URLを取得——既存のrunInpaintExternal/runI2IExternalと同じ「resultImgのsrcを読む」パターンを踏襲)。

Comic Creator側の実装: 14-integrations.jsにrequestLLMPromptFromWorkflowStudio(context)・requestPanelImageFromWorkflowStudio(prompt, width, height)を追加(既存のsendInpaintToWorkflowStudio等と同じloadWfmGalleryTab()→iframe越し呼び出しパターン)。21a-script-manga.jsの「L」ボタンに、シーン・要素・セリフ・現在の画像プロンプトをコンテキストとして渡すハンドラを配線(実行中は「...」表示、成功時は画像プロンプト欄を上書き)。auto-comic-core.jsにpickSdxlResolution(aspectRatio)を追加——sloppy-comicのsdxl_ratio_to_resと同じ考え方で、コマのbboxアスペクト比に対数スケールで最も近いSDXL標準解像度(横長/やや横長/正方形/やや縦長/縦長の5段階)を選ぶ。26-auto-comic-bridge.jsに「画像を一括生成」ボタンを追加。対応付け済みの各コマ(画像プロンプトが空でないもの)について、コマのbboxから解像度を決定→Workflow Studioへ生成リクエスト→結果をnew Image()で読み込んで実寸を取得→state.selectedPanelIdを対象コマへ切り替えてinsertImageで挿入、を順次実行する(sloppy-comicの逐次forループと同じ設計)。一部のコマが失敗しても成功分は反映し、完了後はレイアウトタブへ自動切替。

回帰バグ発見: node --checkでは検出できないESM構文エラー: 実装完了後にKaptureで実機確認したところ、ページ全体がSyntaxError: Unexpected identifier 'shape'という謎のエラーで機能停止する事態が発生。node --checkは全ファイルで問題なしと報告していたため原因特定に時間を要したが、node --experimental-vm-modulesのvm.SourceTextModule(実際のESMパーサー)で全ファイルを検査したところ、22-help-tab.jsのPhase 2で追加した英語ヘルプ文言内にballoons' shapeというエスケープし忘れたアポストロフィ(文字列全体がシングルクォート'...'で囲まれているため、balloons'の'がそこで文字列を閉じてしまい、直後のshapeが裸のJS識別子としてパースされて構文エラーになっていた)を発見・修正した。22-help-tab.jsは01-state.jsからinitHelpTabとしてimportされており、ほぼ全モジュールがimportグラフ経由でこの1ファイルの構文エラーに巻き込まれ、アプリ全体が起動不能になっていた。教訓: node --checkは大きなESMファイルの構文エラーを見逃すことがある(今回のケースでは誤ってOKと報告し続けた)。今後、特に長い文字列リテラルを含むファイル(22-help-tab.js等のヘルプ文言)を編集した際は、node --experimental-vm-modules -e "new (require('vm').SourceTextModule)(src)"のような実際のESMパーサーでの検証か、必ずブラウザでの実機確認を行うこと。

検証: Kaptureで実機E2E確認済み。「L」ボタン: Workflow Studio未接続時はFailed to fetch、Ollama接続済みだが応答が空の場合は専用エラーメッセージを表示することを確認(後者はモデル自体が複雑なプロンプトに対して空応答を返す品質上の問題であり、ブリッジ実装は正しく機能している)。「画像を一括生成」ボタン: Workflow StudioのGenerate UIにt2iワークフローを読み込んだ状態で実行し、「1件の画像を生成しました」の成功メッセージ、レイアウトタブへの自動切替、対象コマへの正しい画像挿入(コマのアスペクト比に応じた小さいタイトルコマにも適切なサイズで挿入)を確認。全操作を通じて新規コードに起因するコンソールエラーは発生しなかった(前述の22-help-tab.js修正後)。

ドキュメント: ヘルプ(22-help-tab.js、日英中3言語)・README(3言語)・PLAN_backlog.md(半自動マンガ作成の未着手項目を削除、3フェーズ全完了)を更新済み。


2026-08-03(半自動マンガ作成 Phase 2: スクリプトのセリフからフキダシを自動生成する機能を追加、v1.23.0)

Phase 1(スクリプト⇄コマ対応付け基盤)に続き、対応付け結果をもとに各コマへフキダシを自動生成する機能を実装した。3フェーズ構成の第二弾(Phase 3: Workflow Studio連携バッチ画像生成は未着手)。

実装方針: 既存のフキダシ生成ロジック(09c-balloon-handles.jsのinsertSmartBalloonTemplate、h2挿入ボタンの手動挿入用関数)とテキスト内包ロジック(09f-bubble-text.jsのapplyBubbleTextToShape、内包テキストモーダルの適用用関数)が、調査の結果ほぼそのままプログラム的呼び出しに転用できることが判明したため、新規実装は最小限に抑えた。insertSmartBalloonTemplateから、フキダシ<g>要素の生成・dataset設定部分を_buildBalloonShapeEl(type, cx, cy, rx, ry)として抽出し、これを使う新規エクスポート関数createBalloonAtPosition(overlaySvgEl, type, cx, cy, rx, ry)を追加(state.selectedPanelIdを対象コマへ設定した状態で呼ぶ規約はinsertImageと同じ)。テキストの流し込みはapplyBubbleTextToShapeをそのまま呼ぶだけで済み、setBalloonTextのような新規関数は不要だった。

配置ロジック: 26-auto-comic-bridge.jsに「フキダシを自動生成」ボタンを追加し、Phase 1と同じmapScriptPageToPanelsで対応付けを行った上で、セリフ(空文字は除く)が入力されている各コマについて、コマのbbox内を上から均等分割した位置に角丸矩形フキダシ(h2タイプrect固定)をセリフ件数分生成する。フォントサイズはコマの分割サイズに収まる範囲で、レイアウトタブの現在のフキダシ既定値(state.balloon.fontSize)を上限に自動調整する。生成完了後は自動的にレイアウトタブへ切り替わる。スクリプト側でのフキダシ形状指定(角丸/思考/バクダン/雲等)はPhase 2の計画通り見送り、固定形状のみ対応。

回帰修正: 前回のメディア種別追加(semiAutoManga)作業時に26-auto-comic-bridge.jsのmediaType !== 'manga'という古いガード条件を更新し忘れており、「半自動マンガ」メディアで「このページをレイアウトに流し込む」ボタンが機能しない状態になっていたバグを本作業で発見・修正した(_scriptIsMangaLikeType()を使うよう修正)。

検証: Kaptureで実機E2E確認済み。メディア種別「半自動マンガ」の作品(4コマ、うち3コマにセリフ入力・1コマは空欄)とテンプレート適用済みページ(実際は10パネル構成)で「フキダシを自動生成」を実行し、「3個のフキダシを生成しました」のアラート・レイアウトタブへの自動切替を確認。生成された3個のフキダシが正しいコマ(panel_2/3/4、空欄だったpanel_1はスキップ)に、正しいセリフ本文(キャラクター名は含めない設計)で生成されていることをDOM調査で確認。ページリロード後もIndexedDB上のpanelSvgContentにフキダシが永続化されていることを確認。新規コードに起因するコンソールエラーは発生しなかった。

ドキュメント: ヘルプ(22-help-tab.js、日英中3言語)・README(3言語)を更新済み。


2026-08-03(半自動マンガ作成 Phase 1: スクリプトタブにメディア種別サブタブ基盤(漫画/半自動マンガ/小説/脚本)+スクリプト⇄コマ対応付け機能を追加、v1.22.0)

参考実装 ComfyUI_sloppy-comic(台本内の{タグ}埋め込みでコマ単位の画像プロンプト・セリフを切り出し、Masonry風レイアウトへ自動合成するComfyUIカスタムノード)を踏まえ、Eagle Comic Creator版の「半自動マンガ作成」機能の実装を開始した。3フェーズ構成(Phase 1: スクリプト⇄コマ対応付け基盤/Phase 2: フキダシ自動配置/Phase 3: Workflow Studio連携バッチ画像生成)の第一弾。

ユーザー要望によるスコープ変更: 当初案ではコマ割りの自動生成(グリッド計算等)を検討していたが、ユーザーの指示により「ユーザーが事前にレイアウトタブで選択・適用した既存テンプレートに対し、コマ1からパネル番号順に処理する」方式に変更した。パネル番号(06c-template-wizard.jsがテンプレートSVGのpolygon id="panel_N"から採番しstate.activePage.panels[].numberとして保持)をそのまま処理順として利用するため、新規のレイアウトアルゴリズムは不要になった。

スクリプトタブのメディア種別サブタブ基盤(ユーザー要望による追加スコープ): 「スクリプトタブの機能拡張も見据え、メディア(漫画の他、小説・脚本台本等)に応じた画面構成サブタブにしたい」という要望を受け、今回のコマ対応付け機能に先立ってこの土台を実装した。データ構造を{ name, synopsis, mediaType: 'manga'|'semiAutoManga'|'novel'|'screenplay', elements, manga: {...}, semiAutoManga: {...}, novel: null, screenplay: null }に拡張し、旧形式(mediaTypeなし、pagesが直下)のデータは読込時に自動的にmediaType:'manga'へ移行・ラップする(_scriptNormalizeData)。メディア種別は作品ごとに固定で、新規作成時のみ選択可能、既存作品読込後はセレクトがdisabledになり変更不可。

続けてユーザーから「半自動マンガ作成用にメディアを追加してほしい(画面構成を半自動向けに変更対応するため)、LLM下書きボタンは両方のメディアで"L"表記にしてほしい」という要望を受け、4つ目のメディア種別「半自動マンガ」(semiAutoManga)を追加した。「漫画」「半自動マンガ」は同じコマ割り編集画面(プロット/プレビュー横/プレビュー縦)を共有しつつ、データはdata.manga/data.semiAutoMangaとして作品ごとに完全に独立させる設計(最小差分案をユーザーが採用)。21a-script-manga.jsに_scriptMangaData()ヘルパーを追加し、現在のmediaTypeに応じてどちらの名前空間を読み書きするかを解決するようにした上で、既存のハードコードされた_script.data.manga参照・mediaType !== 'manga'ガードをすべてこのヘルパー経由に置き換えた(_scriptIsMangaLikeType()を21-script-tab.js側に追加しエクスポート)。「小説」「脚本」は引き続き画面構成が異なる将来実装の予約枠のまま。現時点で編集画面を実装しているのは「漫画」「半自動マンガ」の2種で、他のメディア種別を選択するとプロット関連サブタブに「この機能は開発中です」のプレースホルダーが表示される(要素サブタブはメディア種別非依存の共通機能として存続)。

既存の04a/04b・09a〜09fと同じ「英数字サフィックスでの機能単位分割」慣習に沿い、21-script-tab.jsを作品名・あらすじ・要素・保存読込・メディア種別切替を担う共通層に再編し、漫画/半自動マンガ共通のプロット編集ロジック(ページ/コマ/セリフ管理、旧_scriptRenderPage等)を新規21a-script-manga.jsへ切り出した。両ファイルは相互import(循環)だが、循環先シンボルの参照はすべて関数内部に閉じているため安全(既存の07⇄08パターンを踏襲)。09e-text-tool.js・09f-bubble-text.jsが使っていた_scriptGetSelectedDialogueは_scriptMangaGetSelectedDialogueとして21aへ移動し、import元を更新した。

画像プロンプト欄: コマワリテーブルに「画像プロンプト」列を追加(コマ単位、panel.imagePrompt)。プロンプト下書きボタンも設置したが、Workflow Studio連携によるプロンプト自動下書きはPhase 3で実装予定のため、現時点ではクリックすると未実装アラートを表示するのみ。ボタン表記はユーザー指示により当初の「LLMで下書き」から3Dポーズ側の「RC」ボタンと同様の固定・非翻訳な短縮表記「L」へ変更した(ツールチップ側で説明文を維持)。

コマ対応付け機能: 新規auto-comic-core.js(DOM非依存)にmapScriptPageToPanels(scriptPage, panels)を実装。state.activePage.panelsをnumber昇順ソートし、表示中のスクリプトページのコマと先頭から対応付ける。数が一致しない場合は警告を返し、少ない方の件数までのみ対応付ける。新規26-auto-comic-bridge.js(DOM連携層、01-state.jsのswitchTabから'project'タブ切替時にinitProjectTab()と並んで初期化)がスクリプトタブの「このページをレイアウトに流し込む」ボタンから呼び出し、対応付け結果(コマ番号ごとのセリフ件数)と件数不一致警告を一覧表示する。この時点ではフキダシ・画像はまだ生成しない(Phase 2/3で本機能を拡張する)。

検証: Kaptureで実機E2E確認済み。スクリプトタブでメディア種別を「漫画→小説→漫画」と切り替えてプレースホルダー表示・復元を確認、要素タブがメディア種別非依存で動作することを確認、本機能追加前に保存された既存作品(旧形式データ)を読み込んで自動移行が正しく行われることを確認(コマワリ内容がそのまま復元)、画像プロンプト欄への入力とオートセーブを確認、下書きボタンの未実装アラートを確認、レイアウトタブでページ未選択時の警告アラートを確認、テンプレート適用済みページ(6コマ、サブコマ含む)とスクリプト(4コマ)で「流し込み」を実行し、件数不一致警告と「先頭4コマ分のみ対応付け」の正しい挙動、対応付け結果一覧の表示を確認。追加で「半自動マンガ」を新規作成し、同じコマ割り編集画面(画像プロンプト列・「L」ボタン表記)が表示されること、保存後にdata.semiAutoManga名前空間へ独立して保存されること(data.mangaとは別データ)、保存後はメディア種別セレクトがdisabledになること、既存の「漫画」作品を読み込んで画像プロンプト・ボタン表記ともに正常表示されること(回帰なし)を確認。全操作を通じて新規コードに起因するコンソールエラーは発生しなかった。

ドキュメント: ヘルプ(22-help-tab.js、日本語_HELP_DATA+英中_HELP_I18Nの3言語)・README(3言語)を更新済み。


2026-08-03(3Dテキストの対応フォント形式拡張(TTC対応)・Imageタブのツール未初期化時「coming soon」誤表示を修正)

TTC対応の背景: ユーザーから「読み込めないフォントが結構ある」と報告を受けて調査したところ、原因はopentype.jsがTrueType Collection(.ttc、署名ttcf)形式を一切パースできない仕様だと判明。游ゴシック・游明朝・メイリオ・MSゴシック・MS明朝・BIZ UD系など、Windows標準のCJKシステムフォントの多くがこの形式で配布されている。

実装: text3d-font-loader.jsに_extractFaceFromTTC()を追加。TTCヘッダから先頭書体のテーブルディレクトリと各テーブルの実データを読み取り、オフセットを0起点に振り直して単体sfntバイナリへ再構成してからopentype.parse()に渡す(TTC仕様上、各テーブルの実データはファイル内の絶対オフセットで指されているため、ディレクトリと実データをコピーし直せば単体フォントとして正しく解釈できる)。複数書体入りのファイルは常に先頭書体を採用する(太さ/イタリック別選択は本用途に対して過剰と判断し非対応)。

検証: Node.js上でWindows実機のmsgothic.ttc・meiryo.ttc・YuGothM.ttc・BIZ-UDGothicR.ttcの4フォントを実際にopentype.module.jsでパースし、修正前は全てUnsupported OpenType signature ttcfで失敗、修正後は全て「あ」のグリフ取得に成功することを確認済み(ブラウザでの実機E2Eは未検証)。

coming soon誤表示の背景: ユーザー報告「Imageタブでキャンバス作成前に3DTextツールを選択すると3D Text: coming soonのままなので直してほしい」を受けて調査。

原因: image-tab.jsのthis._text3dTool(および_drawTool/_textTool/_shapeTool等)はキャンバス作成時の_initCanvases()内でのみ生成される。_renderToolOptions()は各ツールIDごとにtoolId === "text3d" && this._text3dToolという形でツールインスタンスの存在を前提条件にしており、キャンバス未作成の状態でツールボタンを押すと(TOOL_DEFSのready:true自体は素通りするため)この条件を満たせず、フォールバックの「coming soon」(未実装機能という誤解を招く文言)が表示されていた。これはtext3dに限らず全ツール共通の構造的な問題だった。

修正: フォールバック分岐でthis._layerMgr(キャンバス未作成時はnull)の有無を判定し、キャンバス未作成時は「先に画像を作成または読み込んでください」という案内メッセージ(image.toolNeedsCanvas、ja/en/zh追加)に差し替えた。キャンバス作成後は既存の_setActiveTool("select")呼び出しでパネルが正しく再描画されるため、ツールを選び直せば正常に動作する。

検証: node --checkで構文確認のみ(ブラウザでの実機E2Eは未検証)。

ドキュメント: ヘルプ(22-help-tab.js、日本語_HELP_DATA+英中_HELP_I18Nの3言語、見出し数・順序を相互比較して確認)・README(3言語)を更新済み。


2026-08-03(3Dテキストにライト・マテリアル設定モーダルを追加、プレビュー品質改善とアンチエイリアス強化トグルを実装)

ユーザー依頼「3Dテキストにもライト、マテリアル設定のモーダルを追加したい(ズーム操作切り替え設定も3Dポーズと共通で)」を受けて実装。

実装: 新規text3d-settings-modal.jsを、3Dポーズ側light_editor.jsのプレビュー埋め込み手法(実際に描画中のcanvasを内包するDOM要素をモーダル内のプレビュー枠へ一時移動し、transform:scale()で表示)を範に軽量実装。マテリアル(色・標準/トゥーン切替・金属/粗さまたは陰色/階調)、ライト(Ambient/Key/Fillの3灯固定、各色・強度・位置)、カメラ(RCボタン・ズーム操作モード)をまとめ、常時表示だったツールバーの対応コントロールは撤去した。ズーム操作モード(ホイール/Ctrl+右ドラッグ)はlocalStorageキーvrmPoseEditor_zoomModeを3Dポーズ側と共有し、どちらで変更しても両方に反映される。text3d-core.jsにライトAPI(getLights/setLightColor/setLightIntensity/setLightPosition)とズームモードAPIを追加。Imageタブ側(Text3DTool.js)はモーダルの変更がthis.xxxのツール内部状態と乖離しないよう、マテリアル系セッターをt3.setXxx()経由でルーティングするアダプタ越しにモーダルへ渡す設計にした。

プレビュー品質の改善とアンチエイリアス強化トグル: ユーザーから「プレビューの拡大表示が粗い、アンチエイリアスのON/OFF切替が欲しい」と指摘を受けて追加調査・実装。原因: transform:scale()でプレビュー枠に合わせて拡大表示する際、レンダラー自体の解像度は据え置きのままだったため、拡大するほど輪郭が粗く見えていた(3Dポーズのライトエディターも同じ手法のため同様の問題を抱えていた)。対策1(常時): 拡大率(scale > 1)に応じて実表示サイズ×devicePixelRatioでレンダラーを都度リサイズし、モーダルを閉じる際は元のサイズへ戻す。対策2(ON/OFFトグル): renderer.setPixelRatio()を通常の2倍(上限4倍)に底上げするスーパーサンプリングを、text3d-core.js・pose_editor_core.js(comfyui-vrm-pose-editorノード側)双方に追加し、localStorageキーvrmPoseEditor_superSampleを共有。設定モーダル/ライトエディターそれぞれにトグルボタンを追加した。

モーダルが閉じられなくなる不具合の修正: 上記プレビュー埋め込み中に「モーダル操作中にエラーが出てフォントが表示されなくなり、モーダルを閉じられない」という報告を受けて調査。cleanup()内のorigParent.appendChild(...)が、何らかの理由でcvsWrapperの元の親要素が失われている場合にorigParentがnullとなり例外を投げ、overlay.remove()まで到達せずモーダルがそのまま画面に残り続けるバグだった。cleanup()全体をtry/catchで保護し、origParentの接続状態を確認した上で復元、失敗時も必ずモーダル自体は閉じるよう修正。同一パターンをコピー元であるlight_editor.js側にも予防的に適用した。あわせて、モーダルを開く際にdocument.activeElement?.blur()を追加し、背後のテキスト入力/フォント選択にフォーカスが残ったまま操作がすり抜ける経路も遮断した。

カメラリセットボタン追加・表記統一: ユーザー依頼によりモーダルにも「RC」ボタン(editor.resetCamera())を追加し、既存のツールバー上の2つのカメラリセットボタン(レイアウトタブ・Imageタブ)を3Dポーズ(pose3d-reset-camera-btn)と同じ「RC」固定表記(非翻訳)に統一した。

検証: 全ファイルnode --checkによる構文チェックのみ。プレビューの拡大時の見た目改善・トグルのON/OFF挙動・モーダル開閉時のレンダリング解像度の復元・カメラリセットの動作は、ブラウザでの実機E2Eが未検証。

ドキュメント: ヘルプ(日英中3言語)・README(3言語)を、下記MToon/カメラ操作改善エントリと合わせて一括で更新済み。


2026-08-03(3DテキストにMToonマテリアルを追加、カメラ操作を3Dポーズに揃えて改善)

MToon追加: ユーザー依頼「3Dテキストにトゥーンシェーダー(MToon)を追加したい」を受けて実装。comfyui-vrm-pose-editorノードが配信するvendor/three-vrm.module.jsからMToonMaterial(GLTFロード無しで単体newできる標準クラス)を動的importし、3Dテキストのマテリアル種別として「標準(MeshStandardMaterial)」と「トゥーン(MToon)」を切替可能にした(text3d-core.jsの_createMaterial()で分岐)。レイアウトタブ・Imageタブ双方のUIにトグルボタンと陰色/階調設定を追加。

カメラ操作の改善: 続けてユーザーから「3Dポーズのようにカメラ操作を行いたい(ズームも可能に、回転スライダーをなくす)」と依頼を受けて対応。原因調査: OrbitControlsのズーム自体は元々有効だったが、テキストや文字サイズを変更するたびに_fitCameraToText()が呼ばれてカメラ位置を強制的に上書きしており、ユーザーがホイールでズームしてもすぐにリセットされる状態だった。修正: orbitのstartイベントでユーザーの手動操作を検知するフラグ(userAdjustedCamera)を導入し、一度でも手動操作した後は編集のたびの自動フィットを止めるようにした(「カメラ位置リセット」ボタンで自動フィットへ復帰)。テキスト自体のX/Y/Z回転機能(params.rotation/setRotation/UIスライダー)はカメラのオービット操作に完全移管する形で削除した。

検証: 全ファイルnode --checkによる構文チェックのみ。MToonの実際の見た目・カメラのドラッグ回転/ホイールズームの挙動は、ブラウザでの実機E2Eが未検証。

ドキュメント: 上記2エントリと合わせ、ヘルプ(22-help-tab.js、日本語_HELP_DATA+英中_HELP_I18Nを見出し数・順序を相互比較しながら更新)・README(3言語、機能一覧・依存関係テーブル・Acknowledgementsを更新)を一括で反映済み。


2026-08-01(スクリプトタブ経由でアセットパネルの「A」「F」タブが非表示のまま戻れなくなる不具合を修正)

ユーザー報告「レイアウトタブ、Imageタブでアセットパネルタブを選ぶと初期のタブを選択することができないAタブがあった」を受けて調査・修正。

原因: 01-state.jsのswitchTab関数で、スクリプトタブ(「S」)に切り替える際、アセットパネルの「A(アセット)」「P(ページ)」「T(テンプレート)」「F(フォント)」「I(画像プロジェクト)」の全ボタンをstyle.display = 'none'にしていたが、他のタブへ戻る際に「A」「F」のdisplayを''(表示)へ戻す処理が漏れていた(「P」「T」「I」は別の条件分岐で個別に制御されていたため無事だった)。そのため一度でもスクリプトタブを開くと、以後レイアウトタブ・Imageタブに戻っても「A」「F」のボタン自体が画面に現れなくなる(activeクラスは付くがdisplay:noneのまま)。git log -Sで調査した結果、このロジックはESモジュール化とは無関係で、v1.0の初回リリース(2026-07-16)から存在していた既存バグと判明した。

修正: switchTabのスクリプトタブ以外の分岐で、assets/fontsボタンのdisplayを明示的に''へリセットするようにした。

検証: Kaptureで実機確認。レイアウトタブ→スクリプトタブ→レイアウトタブ、ページタブ→スクリプトタブ→レイアウトタブ、Imageタブでも「A」「F」「I」が正しく表示・非表示制御されることを確認済み。


2026-08-01(フキダシ「テキストを内包」に主軸整列と独立した上下/左右の配置調整を追加)

ユーザー依頼「レイアウトタブのフキダシのテキストを内包に横書き、縦書きそれぞれ上下、左右、中央にしたい(延長追加で重ねて配置した場合のテキストの位置調整が必要なため)」を受けて実装。

背景: フキダシ「テキストを内包」モーダルには既に「文字寄せ」(横書き=左/中央/右、縦書き=上/中央/下)があったが、これは1軸(主軸)のみで、もう一方の軸(横書き=上下、縦書き=左右)は常に中央固定だった。延長フキダシ機能で複数のフキダシがネックで繋がって重なって配置されるケースで、内包テキスト同士が重なりを避けられるよう、隅へ寄せる調整ができないという問題があった。

実装: 09f-bubble-text.jsに、既存のdata-text-align(主軸)とは独立した新しいデータ属性data-text-valign(副軸、値はtop/center/bottomで統一)を追加。横書き時はそのまま垂直方向(上/中央/下)として使い、縦書き時はラベルを「左/中央/右」に読み替えて水平方向として使う(top→左端寄せ、bottom→右端寄せ)。既存のtextAlignが横書き=水平・縦書き=垂直の値をボタンのdata-align="left"/"right"固定値のままラベルだけ動的に切り替える設計だったため、同じパターンを踏襲した。_bubbleTextRenderTextのレンダリング計算式は、列群/テキストブロックの端をエリアの端(paddingを引いた内側)に合わせる形で導出(例: 横書き上寄せはstartY = area.cy - area.ry + padding + lineHeight * 0.8)。モーダルUIには既存の「文字寄せ」ボタン群の下に、同じ構造の新しいボタン群を追加し、isVertical切り替え時に両方のラベルを同期させる(syncAlignLabelsに対応するsyncValignLabelsを新設)。

後方互換性: 新規データ属性のため、既存の内包テキストは全てtextValign未設定→デフォルト'center'(従来通りの中央配置)になり、見た目は変わらない。

検証: Kaptureで実機確認。既存フキダシのテキストを内包モーダルを開き、横書き時に「上下」ボタン群が正しく表示されること、「上」を選んで更新するとテキストが円の上部に移動すること、縦書きに切り替えるとボタンラベルが「左右」に切り替わること、「左」→「右」の切り替えでテキストが左右に移動することを確認。コンソールエラーはゼロ件。ヘルプ(22-help-tab.js、日英中3言語)・README(3言語)を更新済み。

調整(ユーザーフィードバック): 「フキダシの端は半行(文字サイズの半分)程度開けるのが見た目のバランスがいい。端に寄せすぎない」という指摘を受けて修正。原因: 楕円形状(kind === 'oval'、normal/thought/bomb/cloudpuffy/cloudwavy/textbox-ovalが該当)は対角線方向の実効半径がrx/ryそのものより小さく(内接矩形相当のrx/√2, ry/√2)、折り返し判定用のavailWidth/availHeightは既にこの係数kで補正していたが、寄せ位置の計算(textX/textY/startX/startY)はarea.rx/area.ryをそのまま使っておりkを反映していなかった。そのため上下左右いずれかに寄せると、楕円の先端付近ではテキストが輪郭のすぐ近くまで接近してしまっていた(矩形形状は元々k=1のため影響なし)。修正: hRange = area.rx / k・vRange = area.ry / kを導入し、主軸(align)・副軸(valign)の位置決め計算を全てこれらの値基準に統一。これにより楕円のどの方向に寄せても、輪郭からpadding(fontSizeSvg * 0.5=半行)分のマージンが確保されるようになった。検証: Kaptureで、楕円フキダシに新規テキストを内包し、上/下/左寄せ・縦書きでの下寄せ×右寄せの組み合わせで、輪郭とテキストの間に適切な余白が保たれることを確認。コンソールエラーはゼロ件。


2026-08-01(ESモジュール化 完全後始末: グループ間のwindow.xxxブリッジを全て正式import化)

01-state.jsのESM化完了(本DEVLOG直下エントリ)を受け、G6〜G9で「残課題」として積み残していた、グループ間の既存window.xxxブリッジ経由・暗黙のグローバル参照の呼び出し箇所を全て正式importに置き換えた。これにより、プロジェクト内の非module scriptが1つも存在しない状態(プロジェクト完了)に加え、window.xxxブリッジ自体もゼロ件になった(vendorのUMDライブラリjspdf/jszipが公開するグローバルを除く)。

規模: 対象はstatic/js/main/配下16ファイル+image-tab.js+i18n.js+pixifx.jsの計19ファイル。203件のシンボル参照(symbol-file ペア、ユニークシンボル数122)にimportを追加し、実import化で死んだ分岐になったtypeof X === 'function'存在確認ガードを26箇所削除し、不要になったwindow.X = Xブリッジ定義を120件削除した。

手法(機械化した手順):

  1. 全JSファイルからexport {...}/export function/export constを機械抽出し、symbol→定義ファイルのマップを生成。
  2. 各ファイルのソースからコメント(//・/* */)と文字列・テンプレートリテラルの中身を空白に置換した「ノイズ除去版」を作り、そこから未解決識別子(呼び出し・値参照どちらも単語境界一致で検出、import済み・自己定義のいずれでもないもの)を抽出。ローカルスコープのconst/let/var宣言・関数引数・アロー関数引数・catch節の変数もすべて自己定義として扱い、シャドーイングによる誤検出を防いだ。
  3. 定義元ファイルごとにグルーピングしたimport文を、既存import文群の直後に自動挿入するスクリプトで一括追加。
  4. 追加によりtypeof X === 'function'ガードが死んだ分岐になった箇所を全箇所目視で精査し、意味のある条件分岐(isOverlay &&等)と単純な存在確認を区別しながら手動で削除。
  5. 対象シンボルが「定義元ファイル自身のブリッジ定義行」以外からwindow.Xとして参照されていないかを機械チェックした上で、window.X = X;行を削除。

誤検出との戦い(重要な教訓): 初回の未解決識別子抽出では、コメント除去をしていなかったため34ファイルが該当と過検出になった。コメント除去を追加すると21ファイルまで絞れたが、それでもtという1文字シンボルがimage-tab/DrawTool.js等で大量に誤検出された。原因はconst t = i / steps;(線形補間パラメータ)というローカル変数がi18n.jsのt()関数と同名なだけの偶然の一致で、トップレベル宣言だけを自己定義として扱う簡易チェックではローカルスコープの変数宣言を見逃していたため。関数内部のconst/let/var宣言もすべて自己定義として拾うよう検出ロジックを強化し、最終的に18ファイル・203ペアまで絞り込めた。短い/一般的な名前のシンボル(t, db等)をimport候補として機械抽出する際は、トップレベル限定ではなく関数内ローカル宣言によるシャドーイングも必ず考慮すること。

着手前チェック4点セットの適用結果: (1)トップレベル即時参照チェックは2件ヒットしたが両方コメント内の偶然の文字列一致(04a-mask-core.jsの_maskState定義直後コメント「一度だけpushHistory」等)で実害なし。(4)セッターパターン(別ファイルからの直接再代入)チェックは対象シンボル122件中ゼロ件で、セッター新設は不要だった。

検証: Kaptureで実機確認。ページリロード後の初期化完了、コマ選択(selectPanel)、フキダシ挿入・レイヤーパネル反映(h2InsertBtn→renderHandles/renderLayerPanel)、「テキストを内包」モーダルでのテキスト作成・保存(openBubbleTextModal→savePanelSvg→実際にフキダシへテキストが反映されることを確認)、マスクレイヤー追加、ドローツールでの矩形描画(initGroupManipulation系)、サブコマ分割(initSubPanelManipulation)、フォント管理タブ、Imageタブでの新規キャンバス作成→「レイアウトに戻る」(_saveToLayout→insertImage/switchTab/state.activeWork)を一通り確認し、コンソールエラーはゼロ件だった(「テキストを内包」初回操作時に原因不明のUncaught (in promise)エラーが1件出たが、2回目以降は再現せず、機能自体は正常に動作したため今回の変更との関連は薄いと判断)。検証で作成したテストフキダシ・矩形・サブコマ・画像は全て「元に戻す」(undo)で作業前の状態に復元した。

残る非moduleコード: vendor/jspdf.umd.min.js・vendor/jszip.min.js(UMD形式の外部ライブラリ、window.jsPDF/window.JSZip等を公開)のみ。これらはESM化対象外(サードパーティ配布物)として意図的に維持する。


2026-08-01(ESモジュール化 最終弾: 01-state.js を type="module" 化、段階的移行プロジェクト完了)

G0〜G9(本DEVLOG直下エントリ参照)で01-state.jsを除くstatic/js/main/配下の全ファイルがESM化済みだったところに、初期化オーケストレーター01-state.js(状態管理+初期化+タブ管理、340行)を最後にESM化。これでstatic/js/配下28ファイル・約29,000行の段階的ESM化プロジェクトが完了した。

state/switchTabという2つの中核シンボルが35ファイル超から参照されていた: 01-state.jsは非moduleだった間、stateオブジェクトとswitchTab()関数を暗黙のグローバル参照としてstatic/js/main/配下のほぼ全ファイル・image-tab.js・nanobanana.jsから素の識別子で参照されていた。機械的に依存関係を洗い出した結果、stateは35ファイル、switchTabは7ファイル(image-tab.js含む)から実使用されていることを確認し、全対象ファイルへimport { state, switchTab } from './01-state.js';を追加した。追加は自動化スクリプトで一括実施し、事前にstate.のプロパティアクセスかどうか(ローカル変数やコメント内の"state"との誤混同を除外)・関数引数等によるシャドーイングの有無を機械チェックした上で対象を確定した(i18n.jsのコメント内state.、pixifx.jsのトグルボタン内部変数state、image-tab/FillTool.js・image-tab/ShapeTool.jsのコメントinternal stateは誤検出として除外)。

image-tab.jsの3箇所の暫定ブリッジを正式importへ置き換え: window._ccGetActiveWork?.()(2箇所、作業中の作品情報取得)をstate.activeWorkに、window.switchTab(...)(2箇所)をswitchTab(...)に置き換え、typeof window.switchTab !== "function"という存在確認ガードも実importで保証されるため削除した。window.initImageTab = () => imageTab.init();はexport function initImageTab()に変更し、01-state.js側でimport { initImageTab } from '../image-tab.js';として正式import化。これに伴い01-state.jsが持っていたwindow._ccGetActiveWorkブリッジ自体と、00-db.jsのwindow.openDB/window._setDbブリッジ(01-state.js以外に参照元が無かった)を撤去した。

着手前チェック4点セットの結果: (1)トップレベル即時参照チェックではi18n.jsと22-help-tab.jsの英語ヘルプ文字列内に"...state."という単語が偶然含まれていただけの誤検出2件のみで実害なし。(2)ヘッダコメント非網羅チェックではinitI18nSettingsのヘッダコメント漏れを発見したが外部から参照されていないため実害なし(ヘッダコメントは更新済み)。(3)未解決識別子検出では01-state.js自身の依存関数(30個超、各ファイルの定義元をexportマップとの突き合わせで機械特定)に漏れなし。(4)セッターパターンチェックではstate/switchTabいずれも変数自体への再代入は無く(pixifx.jsのstate = !stateはローカル変数、無関係)、セッター新設は不要だった。

残課題: G6〜G9で積み残されていた、他グループ間の既存window.xxxブリッジ経由呼び出し(例: image-tab.jsがwindow.pixiFxOpen/window.saveToEagle/window.pushHistory等を、19-font-manager.js関連ファイルが_fontMgr系をブリッジ経由で呼ぶ等)は今回のスコープ外として現状維持。これらは呼び出し先が全てESM化済みのため、低リスクな追加清掃としていつでも正式import化できる(PLAN_backlog.mdに記録)。

検証: Kaptureで実機確認。ページリロード後の初期化完了(コンソールエラーなし、Plugin Initializing...→DB connected→NanobananaManager Initializing...→Plugin Initializedの順で完走)、動的importで取得した01-state.jsのstate/switchTabが実際にページ内で使われているモジュールインスタンスと一致すること(state.activeWorkに実データが入っていることを確認)、レイアウト/Image/フォント/設定/スクリプト/ヘルプ/ページ(output)の全主要タブへのswitchTab遷移、Imageタブの「レイアウトに戻る」ボタン(switchTab+state.activeWork経由)操作、をいずれもコンソールエラーなしで確認。検証後はページタブに戻して元の作業状態に復元済み。


2026-08-01(ESモジュール化 第10弾 G9: その他タブを type="module" 化、main/以下28ファイルのESM化が完了)

G0〜G8(本DEVLOG直下エントリ参照)に続く段階的ESM化の第10弾かつ最終コンテンツグループ。対象は 21-script-tab.js(スクリプトタブ、567行)・22-help-tab.js(ヘルプタブ、996行)・23-pose3d-bridge.js(3Dポーズエディタ、575行)・24-sub-panels.js(サブコマ機能、936行)の4ファイル(計約3,070行)。これで01-state.js(初期化オーケストレーター、最終グループとして残置)を除くstatic/js/main/配下の全ファイルがESM化された。

G7・G8に続き「既ESM化済みファイルからの逆方向依存」が今回も多数見つかった: _escHtml(21)は02-assets.js/04b-layer-panel-render.js/06c-template-wizard.js/11a-work-manager.js(G1・G2・G5)から、_scriptGetSelectedDialogue(21)は09e-text-tool.js/09f-bubble-text.js(G4)から、hidePose3DCanvas/_pose3dSyncPosition(23)は11a-work-manager.js/07-pages.js(G5・G3)から参照されていた。特に24-sub-panels.jsは影響範囲が広く、_isSubPanelFrameMode/toggleSubPanelFrameMode/deleteSubPanel/renderSubPanelHandlesが04b-layer-panel-render.jsから、_subPanelCurrentSelected/duplicateSubPanel/moveSubPanelが05-groups-move.jsから、_subPanelSyncBorderWidthUIが08-panels-images.js・09b-balloon-shapes.jsから参照されており、全てgrepで実コード上の使用箇所を確認した上でブリッジ一覧を確定した。

24-sub-panels.jsは既ESM化ファイルへの依存も最多: 17c-layer-draw-handles.js(G7)の draw-shape 用ジオメトリ関数6個、03-layers-panel.js(G1)のロック判定3個、05-groups-move.js(G7で作られたポリゴン中心計算等)2個、06a-polygon-geometry.js(G2)の複製ヘルパー、09b/09c-balloon-shapes.js/-handles.js(G4)のバルーン形状更新・画像transform、00-db.js(G0)のdbPut、04b-layer-panel-render.js(G1)・07-pages.js(G3)・08-panels-images.js(G3)の各種、計14個のシンボルを実importに置き換えた。この過程で_updateH2ShapePathのtypeof存在確認ガードも実importで保証されるため削除した。

新規発見はなし(G7・G8で確立した手法がそのまま機能): 着手前チェック4点セットを全て適用し、実機検証でのランタイムエラーはゼロ件だった。22-help-tab.jsは約860行の巨大な静的ヘルプデータ(_HELP_DATA/_HELP_I18N、英語help文)を含むため、機械的な未解決識別子検出で大量の英単語ノイズが発生したが、実コード部分(末尾約140行)は少数の関数のみで影響は限定的だった。

検証: Kaptureで実機確認。初期化完了(コンソールエラーなし)、スクリプトタブでの既存プロジェクトデータ表示(_workMetaとの実import連携)・プレビュー切替、ヘルプタブでのナビ表示・記事切替・検索・「タブを開く」ジャンプボタン(switchTabとの連携)、レイアウトタブのサブコマツール(ON/OFF切替、initSubPanelManipulationのmousedown/mouseupハンドラ発火確認)、3Dポーズタブ(コマ選択→「コマに配置」→3Dビューオーバーレイの正確な位置合わせ確認、getBoundingBoxFromPointsとの実import連携)・「キャンセル」の一連を確認・エラーなし(3Dビュー表示時に発生したデフォルトVRMモデルファイルの404エラー3件は、このComfyUI開発環境にモデルファイルが未配置なことによるネットワークエラーであり、今回のESM化とは無関係と判断)。


2026-08-01(ESモジュール化 第9弾 G8: フォント管理を type="module" 化)

G0〜G7(本DEVLOG直下エントリ参照)に続く段階的ESM化の第9弾。対象は 19-font-manager.js(フォントマネージャータブ+文字スタイル、930行)・20-font-presets.js(プリセット+初期化+ソース切替、511行)の2ファイル(計約1,440行)。

G7に続き「既ESM化済みファイルからの逆方向依存」が多数見つかった: _fontMgr/_fontMgrCatLabel/_fontMgrCatNames/_fontMgrLoad/_fontMgrGoogleListは09a-balloon-init.js・09d-balloon-tools.js・09f-bubble-text.js・image-tab.js(いずれもG1〜G4で完了済み)から、_fontMgrGroupOpen/_fontMgrToggleGroup/_esc/_fontMgrLoadStyles/_fontMgrRenderTextStylePreviewは09e-text-tool.jsから参照されていた。全てgrepで実コード上の使用箇所を確認した上でブリッジ一覧を確定(G7と同じ「着手前チェック2点目に既存グループからの参照確認も含める」運用を適用)。

新たに発覚した逆方向依存の変種: 19-font-manager.jsの_fontMgrGoogleList()が参照するGOOGLE_FONT_FAMILIES(Google Fonts名一覧のSet)は、意外にも11b-page-manager-tab.js(G5、ページ管理タブ)で定義されていた。typeof GOOGLE_FONT_FAMILIES !== 'undefined'という存在確認ガードで守られていたため、これまで気付かれていなかった依存関係。実importに置き換え、ガード節(およびフォールバックの#font-group-google option要素からの収集コード)は到達不能になったため削除した。

グループ内相互import: 19⇄20で多数の循環importが発生(19は20の_fontMgrRenderPresetStyleSelectを、20は19の25個のシンボルをimportする非対称な依存)。いずれも関数内部での参照に閉じており安全と確認済み。また19は09e-text-tool.js(G4、既ESM化済み)の_fontMgrApplyStyleAttrsToTextElも実importに置き換えた(スタイルプレビューSVGの実描画に使用、_fontMgrRenderStylePreviewSvg関数のコメントに関数名の言及はあったが、呼び出し自体は着手前チェック3点目の機械抽出で発見)。

検証: Kaptureで実機確認。初期化完了(コンソールエラーなし)、フォントタブでのシステムフォント一覧読み込み・フォント選択・基本プレビュー表示、スタイルタブでのSVGプレビュー描画(_fontMgrApplyStyleAttrsToTextElとの実import連携を確認)・線(ストローク)トグルのライブ反映、スタイル保存→プリセット側セレクトへの反映確認(19→20方向のimport)→削除、プリセット保存→削除、タグ追加→削除、「選択テキストに適用」ボタンのガード節(state.selectedTextEl未選択時の警告アラート)の一連を確認・エラーなし。検証で作成したテストスタイル・テストプリセット・テストタグは全て削除し、最終的にページをリロードして検証前の状態に復元済み。


2026-08-01(ESモジュール化 第8弾 G7: 描画/加工(layer-draw)を type="module" 化)

G0〜G6(本DEVLOG直下エントリ参照)に続く段階的ESM化の第8弾。対象は 16-processing-edit-tabs.js(Processingタブ+Editタブ初期化、408行)・17a-layer-draw-input.js(描画初期化+オーバーレイ管理+座標変換+マウスイベント+多角形/ベクター曲線ペン、1301行)・17b-layer-draw-commit.js(SVG要素確定+Undo、345行)・17c-layer-draw-handles.js(draw-shapeハンドル・操作、789行)・17d-layer-draw-paint.js(ペイントツール、463行)・18-svg-color-png.js(SVG色変更+SVG→PNG変換、340行)の6ファイル(計約3,650行)。今回のグループは、17a〜17dが元は単一ファイル17-layer-draw.jsを3分割したもの(+ペイントツールが後日追加され4分割)で、かつ16・18もこの描画機能と密結合していたため、G6までで最も相互import数が多いグループとなった。

着手前チェック4点セットで発覚した特筆すべき点: 従来のG1〜G6では「未ESM化ファイルからESM化済みファイルへの依存」(呼び出し先が先にESM化されるケース)が中心だったが、G7では逆に「既にESM化済みの多数のファイル(03/04a/04b/05/06a/07/08/09c/09d/09e/11a-work-manager.js/15-pixifx-bridge.js、G1〜G6で完了済み)が、まだESM化されていなかったG7側の関数を呼んでいる」というケースが大量に見つかった(clearDrawShapeHandlesは03/04b/05/06a/08/09dの6ファイルから、_layerDrawState/_layerDrawDetachOverlayは07-pages.js・11a-work-manager.jsから、_drawShapeGetBounds/_drawShapeSetBounds/updateDrawShapeHandlesはG6で完成したばかりの15-pixifx-bridge.jsから、といった具合)。これらは非moduleの合成の原理(トップレベルfunction/var宣言が自動的にwindowプロパティになる)により、G7側がまだ非moduleである間は無条件で動いていたが、G7をESM化した瞬間にこの暗黙のwindow公開が失われるため、G7側の各ファイルでwindow.X = Xブリッジを新設しないと、これら6グループ分のESM化済みファイル群が軒並み壊れるところだった。全ての呼び出し元をコード上で確認(コメントのみの誤検出を除外)した上でブリッジ一覧を確定させた。

グループ内の相互import: 17a⇄17b⇄17c⇄17d⇄16の間で多数の循環importが発生した(例: 17aが16の_layerDrawOriginalUnitをimportする一方、16も17a/17d/17b/18の関数をimportする)。全て「循環先シンボルの参照が関数内部に閉じている」ことを確認済み。16は15-pixifx-bridge.js(G6で完成)のopenImageTabWithSelected/openLayoutI2IModal/moveSelectedObjectToCenterも、暗黙のグローバル参照から正式importに置き換えた。

単純な誤検出が多数(重要): 機械抽出した「未解決識別子」の中には、コメント内に関数名を書いているだけの偽陽性が非常に多かった(_drawShapeSyncTexturePatternTransform/_drawShapeSyncProps/_layerDrawFillState/initSubtabs/_fontMgrExtractStyleFromTextEl等)。またimage-tab.jsに_procUpscale/_procApplyDenoise/_procApplySharpenという同名だが無関係な独自ローカル関数が存在しており、単純な文字列一致では別ファイル間の依存と誤認する危険があった。全て実コードを目視確認して除外した。

検証: Kaptureで実機確認。初期化完了(コンソールエラーなし)、ドロータブでの多角形ペンツール(3点クリック→始点クリックで確定、getOrCreateClipGroup/_fontMgrApplyFillPaintToEl/saveOverlaySvg/_layerDrawSelectShape/renderDrawShapeHandlesが正常動作)、OCボタンでの図形中央移動(G6で完成したmoveSelectedObjectToCenterがG7の_drawShapeGetBounds/_drawShapeSetBoundsを正しくimportして連携)、「図形をPNG変換」(convertShapeToImageとの連携)、ペイントツール(「ペイントを追加」→insertImage/_selectClone、ブラシストローク1点)、SVG色変更・SVG→PNG(画像未選択時の警告アラートが正しく表示され、ガード節まで到達することを確認)、Processingタブの「実行」ボタン(同様に警告アラート確認)の一連を確認・エラーなし。多角形描画・PNG変換・ペイント追加で作成したテストオブジェクトは全てレイヤーパネルから削除し、最終的にページをリロードして検証前の状態に復元済み。


2026-08-01(ESモジュール化 第7弾 G6: 外部連携/マンガエフェクトを type="module" 化)

G0〜G5(本DEVLOG直下エントリ参照)に続く段階的ESM化の第7弾。対象は nanobanana.js(Nanobanana API連携、358行)・pixifx.js(PixiJS FXモーダル、938行)・14-integrations.js(Eagle連携+WorkflowStudioギャラリー+G'MIC連携+I2I/Inpaint連携、614行)・15-pixifx-bridge.js(PixiJS FXブリッジ+I2Iモーダル+OCボタン、440行)・15b-manga-tone.js(ハーフトーン変換/生成+マンガ効果、1191行)・15c-manga-bgpattern.js(背景パターン、579行)の6ファイル(計約4,120行)。

pixifx.jsの特殊対応: 他ファイルと異なり (function () { 'use strict'; ... })(); というIIFEでカプセル化された非module scriptだった(先行するG0〜G5には無かったパターン)。モジュールのトップレベルスコープ自体が既にファイル外から隔離されているためIIFEラッパーは不要と判断し、除去した上で window.pixiFxOpen = async function (...) {...} を async function pixiFxOpen(...) {...} + 末尾 export { pixiFxOpen } に変換。内部の大量のヘルパー関数・変数は元の4スペースインデントのまま残し、ラッパー除去のみの最小差分にした。

着手前チェック4点セットを適用: (1)トップレベル即時参照チェック→対象なし(G6の6ファイルへの外部からの呼び出しは全てDOMContentLoadedハンドラ内かボタンのクリックハンドラ内に閉じており、即時評価なし)。(2)ヘッダコメント非網羅チェック→今回も複数件の未記載シンボルを発見(14-integrations.jsの_i2iSettings/_inpaintSettings等、15-pixifx-bridge.jsの_getSelectedImageBlob/_PI2I_DPI等)。(3)未解決識別子検出(呼び出しパターン)→Node.jsスクリプトで機械抽出(grepベースの簡易版、コメント中の関数名らしき文字列を拾う誤検出が複数出たためsaveToEagle/savePanelSvg等は実際のコードで使用箇所を目視確認して切り分けた)。(4)再代入されるモジュール内部変数の再代入チェック→該当なし(G6の6ファイルではセッターパターンの新設は不要だった)。

グループ内の相互依存も実import化: G6は6ファイルを同時に着手したため、ファイル間の呼び出し(nanobanana.js→14-integrations.jsのsaveToEagle/_eagleSettings、15-pixifx-bridge.js→pixifx.jsのpixiFxOpen・14-integrations.jsのgetI2ISettingsState/saveI2ISettingsState/sendI2IRunToWorkflowStudio、15c-manga-bgpattern.js→15b-manga-tone.jsの対象領域決定ヘルパー11個)は全てwindow.ブリッジ経由ではなく最初から正式なimportにした。これに伴い、従来typeof window.X === 'function'で存在確認していた防御的チェックのうち、実importで存在が保証されるようになったものは死んだ分岐として削除した(saveI2ISettingsState/sendI2IRunToWorkflowStudio/pixiFxOpenの3箇所)。

window.pixiFxOpenは維持: image-tab.js(先行グループでESM化済みだが今回のG6スコープ外)がwindow.pixiFxOpen(...)を直接呼んでいるため、pixifx.js側のwindowブリッジは削除せず維持した。同様の理由で14-integrations.jsのsaveToEagle/getI2ISettingsState/saveI2ISettingsState/sendImageToWorkflowStudioI2I/sendInpaintToWorkflowStudio/sendI2IRunToWorkflowStudioのブリッジも維持。image-tab.jsのimport化は次回以降の任意タイミングでの追加清掃候補とし、今回のG6では対象外とした(対象ファイル拡大によるリスク増を避けるため)。

検証: Kaptureで実機確認。初期化完了(コンソールエラーなし)、Nanobananaタブ(APIキー接続確認・タブ切替)、設定タブ(Eagle/G'MIC/Inpaint設定の保存値読み込み)、レイアウトタブでのマンガ効果モーダル(ヴィネット生成→挿入→取消)、背景パターンモーダル(ドットパターン生成→挿入→取消、15c→15bの11個のimportが正常動作)、ハーフトーンモーダル(パターン生成→挿入)、PixiJS FXモーダル(画像選択→フィルタ&パーティクル設定画面のライブレンダリング、IIFE除去後のpixifx.jsが正常動作することを確認)、I2Iモーダル(getI2ISettingsStateによるデフォルトワークフロー設定の読み込み確認)、OCボタン(moveSelectedObjectToCenter)、Workflow Studioギャラリータブ(iframe読み込み・自動タブ選択)の一連を確認・エラーなし。検証で追加した全てのテスト画像・パターンは元に戻すボタンで取消し、ページを検証前の状態に復元済み。


2026-08-01(ESモジュール化 第6弾 G5: 出力/書き出しを type="module" 化)

G0〜G4(i18n.js/00-db.js〜09fフキダシ系、本DEVLOG直下エントリ参照)に続く段階的ESM化の第6弾。対象は 10-output-pages.js(出力管理/ページ一覧/外部ファイル取込、745行)・11a-work-manager.js(作品管理、930行)・11b-page-manager-tab.js(ページ管理タブ、810行)・12-text-png-export.js(テキスト透過PNG変換/画像出力、610行)・13-export-pdf-epub.js(PDF/EPUB出力、320行)・13a-export-metadata.js(画像メタデータ埋め込み、400行)の6ファイル(計約3,800行)。加えて、G4完了時点でwindowブリッジ経由の暫定呼び出しになっていた09a-balloon-init.jsのconvertTextToPng・09c-balloon-handles.jsのembedFontsInSvgを、G3の前例(00-db.js→07-pages.jsのbuildMergedSvg)に倣い正式なimportに置き換えた。

実装: 6ファイルは10⇄11a⇄11b⇄12⇄13⇄13aと相互に密結合しており、複数組の循環importが発生した(いずれも循環先シンボルの参照は関数内部に閉じており安全と確認)。

再代入されるモジュール内部変数が3件見つかった(G0のdbと同じ問題が今回は3倍): _outputFilterGroup(10で定義、11aから再代入)・_outputSelectedPage(10で定義、11bから再代入)・_workSelected(11aで定義、11bから再代入)の3つのlet変数は、定義ファイルとは別のファイルから直接代入(_workSelected = null 等)されていた。importされたバインディングへの代入はできないため、00-db.jsの_setDb()と同じパターンで_setOutputFilterGroup()・_setOutputSelectedPage()・_setWorkSelected()の3つのセッターを新設し、代入箇所をセッター呼び出しに置き換えた。この問題は着手前チェック(grepで変数名\s*=[^=]パターンを機械的に洗い出し、定義ファイル以外からの再代入がないか確認)で全て事前に発見できた。

新しい教訓(着手前チェック4点目): 上記のセッター化作業で、セッター関数(_setWorkSelected等)はimportしたが、読み取り用の変数自体(_workSelected)のimportを一部忘れるミスが発生した。G4で確立した「未解決識別子検出」(check_unresolved_calls.js、関数呼び出しパターンidentifier(のみを対象)はこの種の見落としを検出できず、実機検証で ReferenceError: _workSelected is not defined at renderPageMgrGrid (11b-page-manager-tab.js:662) として発覚した。原因は、セッターパターンを使う変数は「関数呼び出し」ではなく「値としての参照」(_workSelected === nameのような比較)でしか使われないため。対策として、関数呼び出しに限らず単語境界一致(値としての参照も含む)でimport漏れを検出するスクリプト(check_g5_value_refs.js)を新設し、これで_workSelected・_outputSelectedPageの2件のimport漏れを追加検出・修正した。How to apply: 次グループ以降、セッター経由で更新するモジュール内部変数を新設する場合は、「セッター関数」と「変数自体」の両方をimportしたか必ずペアで確認すること。着手前チェックは(1)ヘッダコメント非網羅、(2)export/windowブリッジ差分、(3)未解決識別子(関数呼び出しのみ)、(4)値としての参照も含めた未解決識別子、の4点セットに拡張する。

なお、通常の未解決識別子検出(関数呼び出しパターン)でも、_dbPutRaw(00-db.js)・_saveBlob(13-export-pdf-epub.js)・renderPageMgrGrid(11b-page-manager-tab.js)・svgTextToDataUrl(00-db.js)・_getOrBuildPageThumb(11a-work-manager.js)の計5件のimport漏れを着手前に発見・修正できており、G4に続きこのチェックの有効性が実証された。

検証: Kaptureで実機確認。初期化完了(_workSelectedのimport漏れ修正前は初期化エラーが発生したが、修正後はエラーなし)、出力サブタブでのページ一覧描画・プレビュー表示(buildMergedSvg+svgTextToDataUrl)・ソート基準変更と並び替え(_sortPageOrder、DB保存ログ確認)、作品管理サブタブでのグループ切り替え(ゴミ箱表示、_getOrBuildPageThumbによるサムネイル生成)、作品選択→出力フィルタ設定・解除(_setOutputFilterGroupセッター経由)、JPEG画像出力(handleExport→drawSvgOnCanvas→_embedImageMetadata、ユーザーに保存されたファイルを確認いただき「そのままの名前で保存し確認しました」の回答を得た)の一連を確認・エラーなし。


2026-08-01(ESモジュール化 第5弾 G4: フキダシ/テキストツールを type="module" 化)

G0(i18n.js/00-db.js)・G1(アセット管理/レイヤーパネル/マスクツール)・G2(グループ機能/移動/テンプレート管理)・G3(ページ管理/パネル画像操作、本DEVLOG直下エントリ参照)に続く段階的ESM化の第5弾。対象は 09a-balloon-init.js(初期化、697行)・09b-balloon-shapes.js(図形/パス生成、1353行)・09c-balloon-handles.js(変形/ハンドル操作、740行)・09d-balloon-tools.js(ツール初期化/フォント選択、474行)・09e-text-tool.js(テキスト編集、1053行)・09f-bubble-text.js(フキダシ内包テキスト、594行)・text-style-modal.js(IIFEカプセル化済み、importのみ追加)の7ファイル(計約5,000行)。フキダシ機能全体がESM化された。

実装: G1〜G3と同様の方針。G4内は09a→09b/09c/09d/09e/09fへの依存を中心に、09b⇄09c、09b⇄09e、09b⇄09f、09c⇄09fなど複数組の循環importが発生したが、いずれも循環先シンボルの参照は関数内部に閉じており安全と確認した。text-style-modal.jsはIIFEで完全にカプセル化された構造のため、import { t } from './i18n.js'の追加のみで対応。

着手前チェックの結果: 正規表現によるG4全94トップレベルシンボルの機械抽出、外部参照有無の一括チェック(26件のwindowブリッジ対象を特定)、トップレベル即時参照チェック(該当なし)、export/windowブリッジ差分チェックを事前実施。それでも09a-balloon-init.jsが使うapplyTextInput(09e-text-tool.js定義)のimport漏れは事前チェックをすり抜け、実機検証で「初期化エラー: applyTextInput is not defined」として発覚した。

教訓(新しい検出手法): 上記の見落としを受け、各ファイルの関数呼び出しパターン(identifier()のうち、import・自己定義・既知グローバルのいずれにも該当しない識別子を機械的に検出するスクリプト(check_unresolved_calls.js)を新設。メソッド呼び出し(.foo())がノイズとして混入するため手動精査は必要だが、applyTextInput修正後にこのスクリプトを流し直したところ、saveTextSvg(07-pages.js定義、09a:284行目で使用)のimport漏れも追加で発見できた(window.saveTextSvgはG3で既にブリッジ済みだったため実害はなかった可能性が高いが、念のため明示的にimportへ置き換えた)。How to apply: 従来の「ヘッダコメント非網羅チェック」「export/windowブリッジ差分チェック」だけでは、依存ファイル側の呼び出し漏れ(exportした側ではなく使う側の書き忘れ)は検出できないことが判明した。次グループ以降は「未解決識別子検出」もG1由来の着手前チェック2点セットに加えた3点セットとして標準化する。

検証: Kaptureで実機確認。初期化完了、フキダシ作成(insertSmartBalloonTemplate、尻尾付き楕円フキダシとハンドル表示を確認)、「テキストを内包」モーダル(openBubbleTextModal)でのテキスト入力・作成(applyBubbleTextToShape、フキダシ内に正しくテキストが表示されることを確認)まで機能面はエラーなし。モーダルを開いた際に一度「Uncaught (in promise) The message port closed before a response was received.」がコンソールに記録されたが、これはセッション全体で既知のブラウザ拡張機能由来のノイズ(拡張機能間メッセージングのタイムアウト)と同種であり、アプリケーションコードとは無関係と判断した。検証後、テストで作成したフキダシはレイヤーパネルから削除し後片付け済み。


2026-08-01(ESモジュール化 第4弾 G3: ページ管理/パネル画像操作を type="module" 化、コアSVG統合処理が完全ESM化)

G0(i18n.js/00-db.js)・G1(アセット管理/レイヤーパネル/マスクツール)・G2(グループ機能/移動/テンプレート管理、本DEVLOG直下エントリ参照)に続く段階的ESM化の第4弾。対象は 07-pages.js(ページ管理・buildMergedSvg統合処理・コマ単位保存)と 08-panels-images.js(パネル操作・画像挿入・画像操作)の2ファイル(計約2,400行)。このグループでコアのSVG統合・保存パイプラインが全てESM化された。

実装: G1/G2と同様の方針。07-pages.jsと08-panels-images.jsは相互import(循環)で、循環先の参照はすべて関数内部に閉じているため安全。加えて、G0時点で00-db.jsからwindow.buildMergedSvg(...)という暫定ブリッジ経由で呼んでいた箇所を、正式なimport { buildMergedSvg } from './07-pages.js'に置き換えた(00-db.js⇄07-pages.jsも循環importになるが、双方とも参照は関数内部に閉じており安全)。これでPLAN_backlog.mdに残っていたG0時点の暫定対応を解消できた。

着手前チェックの結果: ヘッダコメント非網羅チェックで、08-panels-images.jsのgetOrCreateDraftGroup(05-groups-move.jsから使用)・saveDraftSvg(07-pages.jsから使用)・selectDraft(04b-layer-panel-render.jsから使用)の3件を着手前に発見。export/windowブリッジ差分チェックでも07-pages.jsの_collectReferencedFilters(05-groups-move.js/09b-balloon-shapes.jsから使用)のwindowブリッジ漏れを着手前に発見・対応した。G1以来の「着手前チェック2点セット」がここでも効果を発揮し、実機検証でのランタイムエラーはゼロ件だった。

検証: Kaptureで実機確認。初期化完了、ページを開く(buildMergedSvg+renderLayoutTab+initPanelsOnSvg)、アセットパネルからの画像挿入(insertImage→insertImageToOverlay)、画像選択・ハンドル表示(renderImageHandles)、レイヤーパネルからの削除、「保存」ボタンでの明示保存(savePanelSvg、ユーザーに確認いただいた「保存しました」アラート)の一連を確認・エラーなし。


2026-08-01(ESモジュール化 第3弾 G2: グループ機能/移動/テンプレート管理を type="module" 化)

G0(i18n.js/00-db.js)・G1(アセット管理/レイヤーパネル/マスクツール、本DEVLOG直下エントリ参照)に続く段階的ESM化の第3弾。対象は 05-groups-move.js, 06a-polygon-geometry.js, 06b-template-manager.js, 06c-template-wizard.js の4ファイル(計約2,600行)。

実装: G1と同様の方針(G2内の相互依存はimport/export、G2外への呼び出しは書き換えず素の識別子のまま+ヘッダコメントに依存元を明記)。G2内部でも循環importが2組できた: 04a-mask-core.js⇄04b-layer-panel-render.jsと同じ判断基準で、06b-template-manager.js⇄06c-template-wizard.jsが相互import(テンプレート管理とウィザードは元々1ファイルだったものを3分割した経緯があり、双方向に密結合)。循環先シンボルの参照はすべて関数内部に閉じているため安全と確認した。

G1の教訓を適用した結果: 着手前に「ヘッダコメントに頼らず全トップレベル定義を機械抽出」を実施したところ、05-groups-move.jsのdeleteSelectedObject/initLayoutDeleteShortcut(01-state.js/07-pages.jsから外部参照)、06c-template-wizard.jsの_tmplGetFrameWidth(内部専用)がヘッダコメントに載っていないことを着手前に発見・対応できた。G1では実機検証まで発覚しなかったが、今回は事前チェックで潰せた。同様に、export一覧とwindowブリッジ一覧の機械的差分チェックも着手前に実施し、_round2(07-pages.js/11a-work-manager.js/24-sub-panels.jsから使用)・_insetPolygonPoints(07-pages.js)・_polygonCenter(24-sub-panels.js)・_selectClone(17d-layer-draw-paint.js)・_cloneWithNewIds(24-sub-panels.js)・layerMove/clearGroupHandles/renderGroupHandles/initGroupManipulation/updateGroupHandlePositions(複数ファイルから使用)・_tmplGroups/initTemplateManager/loadTemplates(01-state.js/11a-work-manager.jsから使用)・_prepareTemplateSvgDocForPage/renderTemplateList/openTemplateWizard/closeTemplateWizard/renameTemplate/deleteTemplate/parseSVGForTemplate(07-pages.js/10-output-pages.js/11a-work-manager.jsから使用)を漏れなくwindowブリッジに含めることができ、G0/G1のようなランタイムエラーが今回は一件も発生しなかった。

検証: Kaptureで実機確認。初期化完了、テンプレート一覧表示(loadTemplates/renderTemplateList/_tmplGetFrameWidth/buildMergedSvg)、テンプレート選択(selectTemplate)、テンプレート作成ウィザードの起動・分割ステップ表示・キャンセル(openTemplateWizard/_tmplWizCreateBase/_tmplWizRender/closeTemplateWizard)、グループアセット挿入(insertGroupAsset+renderGroupHandles)、複製(duplicateSelectedObject+_cloneWithNewIds+_applyOffset+_selectClone)、Deleteキー削除(initLayoutDeleteShortcut+deleteSelectedObject)、レイヤーパネル✕ボタン削除の一連を確認・エラーなし。

How to apply: G1の教訓(ヘッダコメント非網羅、export/windowブリッジ差分の機械チェック)を着手前に前倒しで実施することで、実機検証まで問題が残らずに済んだ。今後のグループでも「着手前チェック」として定着させる。


2026-08-01(ESモジュール化 第2弾 G1: アセット管理/レイヤーパネル/マスクツールを type="module" 化)

G0(i18n.js/00-db.js、本DEVLOG直下エントリ参照)に続く段階的ESM化の第2弾。対象は 02-assets.js, 03-layers-panel.js, 04a-mask-core.js, 04b-layer-panel-render.js の4ファイル(計約1,760行)。

方針転換: G0では未ESM化ファイルへの依存を window.buildMergedSvg(...) のように明示していたが、G1は外部依存が20件を超え機械的置換のコストが高いため、G1外への呼び出しは書き換えず素の識別子のままにした。非moduleのトップレベル宣言(function/var)は自動的にwindowのプロパティになり、それはmoduleのグローバルスコープからも素の識別子で見える(image-tab.jsがt(...)をimportなしに呼べていたのと同じ原理)ため、動作上は問題ない。各ファイル冒頭に未ESM化の外部依存一覧をコメントで明記し、依存の出所が追えるようにした。G1内(4ファイル間)の相互依存はimport/exportにした(04a-mask-core.jsと04b-layer-panel-render.jsは相互import=循環依存になるが、両者とも循環先シンボルの参照はすべて関数内部に閉じており、モジュールのトップレベルでは参照していないため安全と判断)。

実装: 4ファイルをtype="module"化し、それぞれの主要シンボルをexport + window.foo = fooブリッジ(G1外の非ESMファイルから呼べるようにするため)で公開。templates/index.htmlの該当4タグにtype="module"を追加。

発覚した問題と修正:

  1. 00-db.js(G0)のexport漏れ: _enqueueActivePageSave(state.activePageの読み書き直列化キュー)と_dbPutRawが、G0時点でヘッダコメントの「主なトップレベル定義」一覧に載っていなかったためexport/windowブリッジから漏れていた。実機でマスク編集ONにした際、saveOverlaySvg(09b-balloon-shapes.js)→_maskSaveFor(04a-mask-core.js)経由で_enqueueActivePageSave is not definedエラーが発生し発覚。_dbPutRawは11a-work-manager.jsのバックアップ復元処理から使われていることも合わせて判明。両方をexport+windowブリッジに追加した。
  2. 02-assets.jsのexport漏れ: _layoutPreviewSizePct/_applyLayoutPreviewSizeをexportしたがwindowブリッジへの追加を忘れており、まだ非ESMの07-pages.js(renderLayoutTab)が直接呼ぶ箇所で「プレビュー読み込みエラー」として実機発覚。
  3. 03-layers-panel.jsのexport漏れ: _getPanelGroupDomが24-sub-panels.jsから使われているのにwindowブリッジ漏れ。
  4. 04a-mask-core.jsのexport漏れ: _maskAttachOverlayが07-pages.jsから使われているのにwindowブリッジ漏れ。

教訓(重要): 各ファイル冒頭の「主なトップレベル定義」ヘッダコメント(main.js分割時に用意されたもの)は網羅的ではない。アンダースコア始まりの内部ヘルパーが意図的に省略されている場合があり、これを鵜呑みにしてexport対象を決めると本番相当の見落としが発生する。正しい手順は、正規表現(^(async function|function|const|let)\s+\w)でファイル内の全トップレベル定義を機械的に抽出し、それぞれについて他ファイルからの参照有無をgrepで確認すること。G1の4ファイル自体はこの機械抽出とヘッダコメントが一致していたが、G0の00-db.jsだけ一致しておらず、これが今回の全ての見落としの元だった。次グループ着手時は、対象ファイルだけでなく既にESM化済みの全ファイルについてもこのチェックをやり直す価値がある。

検証: Kaptureで実機確認。初期化完了・ページ選択・レイアウトタブでのコマ描画(修正後)・アセットパネル表示・レイヤーパネル表示・マスク編集ON→ブラシ塗り→自動保存(_maskBakeAndSave→saveOverlaySvg→_enqueueActivePageSave)→レイヤー削除(confirmダイアログ経由)の一連を確認・エラーなし。


2026-08-01(ESモジュール化 第1弾: i18n.js / 00-db.js を type="module" 化)

static/js/main/*.js(24分割)ほか計28ファイルが classic <script>(非module、グローバルスコープ共有)のまま肥大化していた件について、ESモジュール化に着手した。バンドラーは導入せず(ComfyUIカスタムノードとしてビルドツール無しの環境でも動く必要があるため)、機能クラスタ単位で段階的に移行する方針をユーザーと合意。第1弾として最も依存の少ない基盤2ファイルを対象にした。

対象を i18n.js + 00-db.js の2ファイルに限定した理由: 当初は 01-state.js も含める想定だったが、精読の結果、同ファイルは DOMContentLoaded ハンドラ内で他23ファイルの init* 関数群(19個以上)を呼ぶ「初期化オーケストレーター」であることが判明。ESM化すると呼び出し箇所全部に window. プレフィックスを付けて回る必要があり、書き忘れリスクが最も高い。他の全グループのESM化が終わってから最後に着手する方が、window.xxx() と書いてすぐ import に直す二度手間も避けられるため、今回は対象から外した。

実装:

  • static/js/i18n.js: type="module" 化。t/resolveBackendError/getLang/setLang/getLanguageOptions/applyI18nToHtml を export しつつ、まだESM化されていない classic <script> 側(42ファイルが t() に依存)から呼べるよう window.t = t 等のブリッジを同時設置。
  • static/js/main/00-db.js: type="module" 化。DB_NAME/DB_VERSION/openDB/dbGet/dbPut/dbDelete/dbGetAll/dbGetAllPagesMeta/readFileAsText/readFileAsDataURL/svgTextToDataUrl を export+windowブリッジ。内部の db はモジュールスコープに閉じ込め、外部(01-state.js)から更新するための _setDb(d) セッターを新設(db はexport後に再代入されるため直接exportできない)。buildMergedSvg(07-pages.js、未ESM化)への依存は window.buildMergedSvg(...) 経由の暫定呼び出しに変更。
  • static/js/main/01-state.js: db = await openDB(); の1行のみ window._setDb(await window.openDB()); に変更(ファイル自体は非moduleのまま)。
  • templates/index.html: 上記2ファイルの <script> タグに type="module" を追加。

発覚した問題と修正(static/js/main/11a-work-manager.js): type="module" スクリプトは常にdefer相当のタイミングで実行されるため、i18n.js(従来は非module・文書内で最も早く同期実行される想定だった)が、後続の非moduleスクリプト群より後に実行されるようになった。これにより、11a-work-manager.js のトップレベル(関数外、モジュール読み込み時に即時評価される部分)にあった const TRASH_GROUP_LABEL = t('page.trashLabel'); と、WORK_SIZE_PRESETS 配列リテラル内の6箇所の t(...) 呼び出しが、i18n.jsのモジュール実行(window.t ブリッジ設置)より先に走り ReferenceError: t is not defined で初期化が失敗、後続の const STOCK_GROUP も未初期化のままTDZエラーを誘発した。_trashGroupLabel() / _reservedGroupNames() / _workSizePresetList() という遅延評価関数に変更し、呼び出し時に都度 t() を評価するよう修正(11a-work-manager.js、使用箇所の 11b-page-manager-tab.js 側4箇所も追随)。事前のgrep調査(^t\( 等の行頭一致)ではこの2箇所を検出できておらず、ブレース深さを追跡するNode.jsスクリプトで全ファイルを再チェックして他に無いことを確認した。

検証: Kaptureで実機確認。初期化完了(DB connected → Plugin Initialized、エラーなし)、ページ一覧・サムネイル表示、ページを開く(レイアウトタブでのコマ描画、buildMergedSvg ブリッジ経由)、保存(dbPut、「保存しました」アラートをユーザーに確認いただいた)、言語切替(en⇄ja、setLang/getLang → location.reload())を確認・承認済み。

How to apply: 非moduleスクリプトのトップレベル(関数外)で他ファイルのグローバル関数を即時評価しているコードがないか、対象ファイルをESM化する前に必ず確認すること。type="module" 化は実行タイミングを常にdefer相当へ変えるため、それより前に書かれた非moduleスクリプトのトップレベルコードの方が先に実行されるようになる(読み込み順が保たれるという直感に反する)。検出時は単純な正規表現(行頭一致)だけでは配列/オブジェクトリテラル内の呼び出しを見逃す(今回WORK_SIZE_PRESETSの1件がまさにこれで、2回に分けて見つかった)。トップレベルの const/let/var 宣言はブロック全体(複数行にまたがる場合を含む)を対象にチェックすること。

残作業: 01-state.js を含む残り26ファイルは引き続き非module。今後、機能クラスタ単位(フォント管理、フキダシ、ページ/出力、描画系など)で段階的にESM化を進める。詳細な区分は PLAN_backlog.md 参照。


2026-07-30(延長フキダシの孫(3段以上の連結)で境界線マスクが効かない不具合を修正)

延長フキダシ機能で、ベースに延長を追加しさらにその延長へ延長を追加する(親→子→孫の3段連結)と、親+子の直接連結では正しく効いていた「重なり部分の境界線を消して外周だけ1本の線にする」共有リング処理が孫では機能せず、継ぎ目やネックの取り残しが起きるとの報告を受けて調査・修正した。

原因(static/js/main/09b-balloon-shapes.js) _updateH2ShapePath()末尾のチェーン追従処理が、dataset.linkedToIdを1段だけ辿ってチェーンの基点を求めていた(document.getElementById(el.dataset.linkedToId))。孫フキダシ(子フキダシにリンクした延長)の場合、この基点は真のルートである親ではなく「子」になってしまう。同様に共有リングを生成する_updateChainUnionRing()も直接の子だけを集めて合成していたため、親+子で1つ、子+孫で別の1つという2つの独立した共有リングが作られてしまい、本来1本であるべき外周に継ぎ目が出たり、孫側のネックが再計算されず取り残されたりしていた。親に2つ直接延長した場合はどちらも直接親にリンクするため1つのリングで済み、この問題は起きていなかった。

修正

  1. _h2ChainRootEl(el)(新規): linkedToIdを最上位(リンク元を持たない要素)まで辿るヘルパーを追加。循環参照やベース削除済みのケースはnullを返す。
  2. _h2ChainAllDescendants(rootEl)(新規): ルート要素からdata-linked-to-idで直接・間接に連なる延長(子・孫・ひ孫...)を再帰的にすべて収集するヘルパーを追加。
  3. _updateChainUnionRing()の集計対象を「直接の子のみ」から_h2ChainAllDescendants()に変更し、チェーン全体を1つの共有リングにまとめるようにした。
  4. _updateH2ShapePath()末尾のチェーン追従処理を、1段だけ辿る基点解決から_h2ChainRootEl()によるルート解決に変更。ネックの再計算対象も_h2ChainAllDescendants()で孫以降まで含めるようにした。
  5. ついでに見つかった同種の不具合2件も同じヘルパーで修正: _h2CleanupBalloonChainBeforeDelete()(09b-balloon-shapes.js)の削除カスケードが直接の子までしか道連れにしておらず、途中のフキダシを削除すると孫が孤立して残っていた点、およびstatic/js/main/08-panels-images.jsのベースドラッグ時の平行移動カスケードが直接の子までしか追従しておらず、ルートをドラッグすると孫が置き去りになっていた点。

検証: ユーザーが実機で親→子→孫の3段連結を作成し、修正後は親に2つ直接延長した場合と同様に重なり部分の内部線が出ない1本の外周表示になることを確認・承認済み。

How to apply: このフキダシ連結機能で「直接の子」だけを見るコードを新たに書く場合、親→子→孫のような3段以上の連結で必ず抜け漏れが起きる(今回3箇所で同種のバグが見つかった)。dataset.linkedToIdを辿る/集計するロジックは_h2ChainRootEl()/_h2ChainAllDescendants()に一本化すること。


2026-07-30(レイアウトタブのSVG色変更機能で、色を変更・保存し再度開くとSVG画像が消える不具合を修正)

ユーザーから「レイアウトタブのドローツールでSVG色変更を行い保存した後、再度開くとSVG画像が消える」との報告を受けて調査・修正した。

原因(static/js/main/18-svg-color-png.js) _svgColorApply()が色変更後の画像要素にhrefとxlink:hrefの両方をsetAttributeしていた。HTMLドキュメント上のライブDOM要素に対してsetAttribute('xlink:href', ...)を使うと、名前空間が束縛されていない生の属性になる(setAttributeNSではないため)。保存時(savePanelSvg/saveOverlaySvg、07-pages.js)はXMLSerializerでシリアライズするがxmlns:xlink宣言は付与されないため、保存データは不正なXMLになる。再度開く際、renderLayoutTab()が内部でDOMParserの厳格XMLモード('image/svg+xml')でこの文字列を再パースすると、名前空間エラーで<parsererror>ドキュメントになり、querySelector('svg')がnullを返してそのパネル/オーバーレイの内容ごと黙って読み飛ばされていた。通常の画像挿入処理(08-panels-images.js)はhrefのみをセットしており、この不具合はSVG色変更機能に固有だった。

修正 _svgColorApply()からxlink:hrefのセットを削除(既に付いていればremoveAttributeで除去)。他の画像挿入経路と同様にhrefのみをセットするようにした。

既知の制限: この修正は今後の色変更・保存が正しく行われることを保証するのみで、修正前に既にこの不具合で保存済みのSVG画像は、次にそのパネル/オーバーレイを保存した際に空のSVGとして上書きされる形で既に消えている可能性がある。該当ページがあれば個別に復旧を検討する必要がある(本セッションではユーザーの実データに該当は見つからなかった)。

How to apply: SVG文字列をライブDOM要素に対して構築・保存する際、xlink:hrefのような名前空間付き属性はsetAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', ...)を使うか、そもそもhrefのみで足りる場合は付与しないこと。setAttributeで名前空間プレフィックス付きの属性名を設定すると、シリアライズ後に厳格XMLパーサ(DOMParserの'image/svg+xml'モード)で再パースした際に静かに全内容を失う事故になる。


2026-07-30(フキダシに「尻尾なし」チェックボックスを追加、ツールバーの「削除」ボタンを画像専用から全オブジェクト対応に汎用化)

延長フキダシ機能まわりの一連のバグ修正(本DEVLOGの直下3エントリ参照)に続く追加要望2件。

実装1: 「尻尾なし」チェックボックス(templates/index.html, static/js/main/09a-balloon-init.js, 09c-balloon-handles.js) フキダシ選択時の尻尾パラメータ行(長さスライダーの右隣)にh2-tail-noneチェックボックスを追加。ONにすると現在の長さをdataset.tailLengthPrevに退避したうえでdataset.tailLength = 0にし、長さスライダーを無効化する。OFFに戻すと退避しておいた値(無ければ既定の60)を復元し、スライダーも再度有効化する。_syncH2UI(09c-balloon-handles.js)にチェック状態・スライダー無効化状態の同期も追加し、フキダシの選択切り替え時に正しく反映されるようにした。延長フキダシの既定(tailLength=0)とも自然に整合する。

実装2: ツールバー「削除」ボタンの汎用化(static/js/main/07-pages.js, templates/index.html) レイアウトタブ上部ツールバーの「削除」ボタン(id: delete-image-btn)は、画像専用のdeleteSelectedImage()という独立した関数を呼んでおり、フキダシ・テキスト・図形・グループを選択していても「画像が選択されていません」と表示されるだけで削除できなかった。ボタンのクリックハンドラを、Delete/Backspaceキーと共通の汎用関数deleteSelectedObject()(05-groups-move.js。画像・フキダシ・テキスト・図形・グループすべてに対応し、延長フキダシの連結解除等の後始末も含む)を呼ぶように変更し、未選択時のメッセージも汎用的な文言(layout.msgNoObjectSelected)に差し替えた。旧deleteSelectedImage()関数は他に呼び出し元が無いことを確認のうえ削除。

検証: Kaptureで両機能を実機確認。尻尾なしチェックボックスはON/OFFで尻尾の表示・非表示と長さの復元、保存後の永続化を確認。削除ボタンはフキダシ選択時の削除成功、未選択時のアラート表示をそれぞれ実際のクリック操作で確認済み(画像削除については既存の共通関数を流用するのみのため、コードレビューで回帰が無いことを確認)。


2026-07-30(延長フキダシ削除時、レイヤーパネルの✕ボタン経由だとネック・共有リングが消し忘れられる不具合を修正)

前2件の共有リング関連バグ修正(本DEVLOGの直下エントリ2件参照)に続く3件目の関連バグ。ユーザーから「フキダシ削除後にフキダシ形状の黒の表示、ネックのみのゴミ、フキダシ・延長フキダシのあった場所に矩形が残り、保存して再度開いても表示が残る」との報告。

原因: 延長フキダシの道連れ削除・ネック(コネクタ)・共有リング/マスクの後始末はdeleteSelectedObject()(05-groups-move.js、Delete/Backspaceキー経由)にのみ実装していたが、レイヤーパネルのフキダシ行の✕ボタン(04b-layer-panel-render.js)はshape.remove()のみを行う完全に別の削除コードパスで、この後始末を一切経由していなかった。✕ボタンで削除すると、道連れになるはずの延長フキダシ、ネック(.balloon-connector-fill/.balloon-connector-border)、共有リング(<rect id="chain-ring-...">)、共有マスク(<mask id="chain-mask-...">)が全て孤立したまま残り、保存されるとその状態が永続化されてしまう。孤立したリングは対応するフキダシが消えているため_h2ChainAnchorNode等の追従処理も働かず「矩形」として、孤立したネックは接続先が無いまま固定形状の「ゴミ」として、フキダシ本体を削除し忘れた側では枠線非表示のまま孤立して「黒塗り」として見えていた。

修正(static/js/main/09b-balloon-shapes.js, 05-groups-move.js, 04b-layer-panel-render.js) 削除前後の後始末を_h2CleanupBalloonChainBeforeDelete(el)(道連れ延長・ネック・共有リング/マスクの削除、戻り値はelが延長なら紐づくベースid)と_h2RefreshChainAfterDelete(linkedToId)(削除後にベースを再描画し枠線表示/共有リングを更新)の2関数に共通化し、deleteSelectedObject()とレイヤーパネル✕ボタンの両方から呼ぶように統一。今後どちらの削除経路を使っても同じ後始末が保証される。

残存ゴミの調査・除去: Kaptureのevaluateで全ページ(IndexedDB内pagesストア)を横断的にスキャンし、フキダシ本体が存在しないのに残っているchain-ring-*/chain-mask-*/.balloon-connector-*要素を検出するスクリプトを実行。ユーザーの1ページ(3コマ分)で該当を発見し、ユーザー確認のうえその場でSVG文字列から孤立要素のみを取り除いて保存し直した(該当コマ以外・他ページへの影響なし)。修正後、全ページ再スキャンで0件を確認。

検証: Kaptureで実際にレイヤーパネルの✕ボタンをクリックする操作(今回のバグ発生と同じ経路)で延長フキダシを削除し、道連れ削除・ネック除去・共有リングの再構築(残り数に応じて)が正しく行われること、保存→DB直接読み返しでゴミが残っていないことを確認済み。

How to apply: 同じ種類のオブジェクト(フキダシ、画像等)に対する削除操作が複数の入口(キーボードショートカット、レイヤーパネルのボタン、等)を持つ場合、道連れ削除や付随要素の後始末を伴うロジックは、片方にしか実装していないと今回のように別経路から漏れる。新しい付随要素(ネック・共有リング等)を追加する際は、既存の削除コードパスをgrep等で全て洗い出し、共通ヘルパー関数に切り出して全ての入口から呼ぶこと。


2026-07-30(延長フキダシの共有リング・ネックが、画像挿入や重ね順変更で他オブジェクトに隠される不具合を修正)

直前の共有リング関連バグ修正(本DEVLOGの直下エントリ参照)の直後、別のユーザー報告で発覚した2件目の関連バグ。「フキダシ配置後、画像を挿入し最背面に移動したところ、フキダシの線が画像との境界で表示されなくなる」との報告。

原因: _updateChainUnionRing()/_updateBalloonConnector()(09b-balloon-shapes.js)が生成する共有リング・ネックは、常に「親要素の先頭(=コマ/オーバーレイ内で絶対的な最背面)」に固定配置されていた。これは、レイヤーパネルの重ね順操作(画像を最背面へ移動、等)が対象オブジェクトをレイヤーパネルに表示される実オブジェクトの中でのみ並べ替える一方、共有リング・ネックはレイヤーパネルに存在しない実装上の要素であるため、その並べ替えの対象外になる。結果、「画像を最背面へ」という操作が、レイヤーパネル上のフキダシより後ろ・かつリング/ネックより前という中間位置に画像を割り込ませてしまい、画像がリング/ネックを覆い隠して線が消えた。

修正(static/js/main/09b-balloon-shapes.js) 新関数_h2ChainAnchorNode(parent, targetNodes)を追加。ベース・延長のうち現在のDOM順で最初に現れる(=最も奥にある)ノードを毎回探索し、共有リング・ネック(塗り→縁取りの順)をその直前に固定するよう変更(従来の「parent先頭に固定」から「対象シェイプ群の直前に固定」へ)。呼び出しのたびに再計算するため、画像挿入や重ね順変更で他オブジェクトが間に割り込んでも、リング・ネックは常にベース/延長のすぐ奥に留まる。あわせて_updateH2ShapePath末尾のフックの呼び出し順を「リング→ネック」に統一(リングを後から挿入すると、先に挿入したネックより手前に来てネックを隠してしまうため)。

副次的に発覚した検証手順上の注意点: 修正確認のためKaptureのevaluateで対象フキダシにsavePanelSvg(panelId, svgEl)を直接呼ぶ際、panelIdをstate.selectedOverlay等のUI状態から推測すると誤ったコマ/オーバーレイに保存してしまう(対象フキダシはdata-clip-panel属性を持つコマの中にあり、オーバーレイ上ではなかった)。誤ったオーバーレイへの保存は幸いこのケースでは実害が無かった(保存先が元々ほぼ空だったため)が、修正確認の保存操作では必ずshape.closest('g[data-clip-panel]')?.getAttribute('data-clip-panel')(無ければ'__overlay__')で対象を特定してからsavePanelSvgを呼ぶこと。

検証: Kaptureのevaluate機能でユーザーの実データ(panel_6内、画像を最背面へ移動済みの延長フキダシペア)に対し、修正後のコードで_updateH2ShapePath()を再実行→線が表示されることを確認→正しいパネルID(panel_6)でsavePanelSvg()→リロード→DOM順・見た目とも維持されていることを確認済み。

How to apply: レイヤーパネルに現れない「実装上の補助要素」(共有リング、ネック、その他の合成用path/rect等)をSVGに追加する場合、単純に「parentの先頭/末尾に固定」する設計は、レイヤーパネル経由の重ね順操作や新規オブジェクト挿入によって他要素がその補助要素と本来のオーナー要素の間に割り込む形で壊れる。補助要素は常に「オーナー要素(群)のうち現在のDOM順で最初/最後に現れるものを起点に、毎回位置を再計算して配置し直す」設計にすること(_h2ChainAnchorNodeのパターンを参照)。


2026-07-30(延長フキダシの共有リングが保存のたびに壊れ、コマ/オーバーレイの背景が黒くなる不具合を修正)

直前の「延長フキダシ」機能追加(本DEVLOGの直下エントリ参照)で発生した重大な回帰バグ。ユーザーから「フキダシ配置後に画像を挿入したら全コマの背景が黒くなり、フキダシの枠線も消えた。フキダシを動かすと背景は戻るが線は直らない。別ページでも再現する」との報告を受けて調査・修正した。

原因: _updateChainUnionRing()(09b-balloon-shapes.js)が生成する<mask id="chain-mask-{baseId}">に、保存処理(savePanelSvg/09b-balloon-shapes.js内のsaveOverlaySvg)が保存対象のマスクを判定するために使うdata-ccc-mask属性を付け忘れていた。両保存関数はmask[data-ccc-mask]かつ値がコマ/オーバーレイ内の要素idと一致するものだけをdefsに持ち回る仕様(04a-mask-core.jsのレイヤーマスク機構と共通の規約)で、この属性が無いマスクは保存の度に静かに欠落する。結果、連結範囲だけを型抜きするはずの共有リング<rect>(ページ全面サイズで作成し、mask属性で連結範囲だけに絞り込む設計だった)が、参照先のmaskを失ってmask無しの状態=ページ全面がstrokeColor(黒)で塗りつぶされた状態で描画されてしまい、フキダシ自身の枠線も個別には非表示にしている設計(共有リングに依存)のため、黒一色に埋もれて見えなくなっていた。画像挿入自体は直接の原因ではなく、保存(savePanelSvg/saveOverlaySvg)を伴うあらゆる操作で発生し得た。

修正(static/js/main/09b-balloon-shapes.js _updateChainUnionRing())

  1. <mask>生成時にdata-ccc-mask属性(値はベースのid)を付与し、保存対象として正しく持ち回られるようにした。
  2. 併せて、リング<rect>のサイズをページ全面から「チェーン各メンバーの外接矩形+余白」に縮小。同種の不具合が万一再発しても被害をその範囲に留める防御策。

検証: Kaptureのevaluate機能でユーザーの実データ(オーバーレイ上の壊れたチェーン、chain-ring-shape-1785340943021が存在しないmaskを参照していたことを直接確認)に対し、修正後のコードで_updateH2ShapePath()を再実行→表示が正常化→savePanelSvg('__overlay__', ...)で保存→リロード→chain-mask-shape-1785340943021が正しくdefsに残っていることを確認済み。

既知の制限: この修正は今後の編集で正しく保存されることを保証するのみで、修正前に既に壊れた状態で保存済みの他ページ・他作品のチェーンは自動修復されない(該当のベースまたは延長フキダシを一度選択して少し動かす/リサイズすると、その場で正しく再生成され次回保存時から直る)。全ページを走査する一括修復スクリプトの実行はユーザーの判断で見送り(必要になれば都度対応)。

How to apply: このアプリで新規に<mask>や<clipPath>等の<defs>要素を保存対象のSVGコンテンツ内に追加する場合、savePanelSvg(07-pages.js)・saveOverlaySvg(09b-balloon-shapes.js)は「白紙から保存用SVGを再構築し、明示的に収集したdefsだけを persist する」方式のため、新しい<defs>要素は保存関数側にも対応するコピーロジックを追加しない限り、次の保存で静かに失われる。既存のマスクはdata-ccc-mask属性の値(対象パネルid/__overlay__、またはそのパネル/オーバーレイ内に存在する要素id)で持ち回り判定されているため、パネル・オーバーレイに紐づくマスクを新設する場合はこの規約に乗るのが最も安全(クリップパスなど別種のdefsを追加する場合は、両保存関数の収集ロジックそのものを拡張する必要がある)。


2026-07-30(レイアウトタブのフキダシに「延長フキダシ」機能を追加)

Comic Life(市販マンガ作成アプリ)の「延長フキダシ」を参考に、1つのフキダシに同じ形状の追加フキダシをネック(連結部分)でつないで延長できる機能を追加。長いセリフを複数のフキダシに分けて配置したいという依頼に対応した。

実装: 延長の追加(09c-balloon-handles.js addExtensionBalloon()新規、09a-balloon-init.jsでボタンイベント登録、templates/index.htmlにh2-add-extension-btn追加) 選択中のフキダシ(ベース)と同じshapeType・色・タイプ別パラメータをコピーした新規フキダシを、ベース中心から右下にオフセットした位置に生成。dataset.linkedToIdでベースのIDを保持し、tailLength=0にして自身の突き出し尻尾は非表示にする(ベースとの連結はすべてコネクタが担う)。ドロップダウン→挿入ボタンという既存のフキダシ挿入UIパターンに合わせ、Comic Lifeのようなパレットからのドラッグ&ドロップ方式は採用しなかった(ユーザー承認済み)。

実装: ネック(コネクタ)の描画(09b-balloon-shapes.js _updateBalloonConnector()新規) ベースと延長、両方の現在位置・回転角から境界点を毎回動的に計算し、閉じた蝶ネクタイ形のパスとして描画する。フキダシとの接合部分に境界線が見えてしまう問題があったため、塗り(.balloon-connector-fill、閉じた形、stroke無し)と縁取り(.balloon-connector-border、実際に露出する自由端2曲線のみをオープンパスでstroke)に分割し、接合部分には一切線を引かないようにした。_updateH2ShapePath()の末尾に、自身が延長ならコネクタを、自身がベースなら延長側のコネクタも合わせて再計算するフックを追加。リサイズ・回転・尻尾ドラッグ・本体移動などフキダシの見た目が変わるすべての経路がこの関数を通るため、この一箇所で追従を担保できる。

実装: 位置の追従ルール(08-panels-images.jsの本体ドラッグ処理) ユーザーとの検討の結果、次の3ルールで確定: ベースを移動すると延長も同じ量だけ平行移動する(相対位置を保持)/ベースをリサイズ・回転しても延長の位置には影響しない/延長を移動するとベースは動かずネックだけが伸縮する。本体ドラッグのmousedown/mousemoveに、ドラッグ対象がベースの場合はリンク済み延長の初期中心を記録し同じdx/dyを適用するカスケード処理を追加。延長側をドラッグした場合はこのカスケードが空振りし、コネクタだけが再計算される。

実装: 尻尾が延長の外周まで届く(09b-balloon-shapes.js _h2TailBoundaryPoint()/_h2RayExitDistance()新規) フキダシ本来の尖った尻尾を、ベースから延長側へドラッグしたときに連結全体の外周まで届かせたいという要望に対応。ベースから尻尾方向へレイを飛ばし、その方向に延長フキダシがあれば境界サンプリング+二分探索で延長側のより外側の交点を境界点として返す汎用関数を実装し、尻尾関連の境界点計算7箇所(本体描画のnormal/rect/cloudpuffy/cloudwavy分岐、ハンドル位置計算、尻尾ドラッグ処理)を置き換えた。bomb/thoughtタイプは独自のスケール処理のため今回は対象外(既知の制限)。

実装: フキダシ同士が重なる場合の内部の線を消す(mask方式)(09b-balloon-shapes.js _updateChainUnionRing()新規) ネックのみの修正では、延長を大きく重ねて配置した場合にベース・延長それぞれの独立した輪郭線が交差して見える問題が残っていた。SVGにはパスの論理和(union)を作る機能が無いため、<mask mask-type="alpha">にベース+全延長の本体・尻尾パス(.h2-bg-body/.h2-bg-tailのdをそのまま複製し、各自のangleでtransform="rotate(...)"を付与)を集約し、枠線太さ分だけ外側に膨らませたシルエットでstrokeColorの全面矩形を型抜きする共有リング(<rect id="chain-ring-{baseId}">)を実装。連結されているフキダシは個別の枠線を非表示にし(_updateH2ShapePathにhasChainPartners判定を追加)、各自のfill-layer(内側の白塗り)だけを通常どおり最前面に描画することで、結果的に連結全体の外周だけが1本のリングとして見えるようにした。延長を削除した際は05-groups-move.jsの削除処理からベースを再描画し、枠線表示・共有リングを最新の連結状態に合わせて更新する。

検証: Kaptureで実機確認。フキダシ選択→延長追加でネック付き延長が正しく生成されること、延長のみ削除でコネクタごと消えベースが残ること、ユーザー自身の実データ(「いいい」「ああああ」の連結フキダシ)でネック・重なり部分の余計な線が解消されたことを確認済み(ユーザー承認)。ベース/延長のドラッグ移動・リサイズ・回転・尻尾ドラッグによるカスケード追従は、コードレビューでロジックを確認したのみで、このセッションのKaptureにドラッグ操作を模擬する手段が無く実機での網羅的な検証はできていない。

How to apply:

  • SVGでは複数の独立した閉じた形状が重なると、それぞれの輪郭線がそのまま交差して見える。「連結された複数図形をまとめて1つの輪郭として見せたい」場合は、個別の枠線を非表示にしたうえで<mask mask-type="alpha">に各図形の本体パスを複製・集約し共有の「外周リング」を作る、という今回のパターンが再利用できる(本ノードにはパスの論理和を作る手段が無いため)。
  • 延長フキダシの追加以降、そのフキダシが「見た目が変わるすべての経路が通る1つの関数」(本アプリでは_updateH2ShapePath)にコネクタ・共有リングの再計算フックを仕込むと、個別の呼び出し元(ドラッグ・リサイズ・回転処理)を変更せずに追従を担保できる。

2026-07-29(設定タブ「Inpaint設定」見出しの英語・中国語訳の欠落を修正)

直前のSelect I2I/レイアウトI2Iモーダル追加作業で発見していた既知の翻訳漏れ(_HELP_I18N.en/.zhに設定タブ「Inpaint設定」見出しが一件も存在せず、日本語版_HELP_DATAにしか無かった)に対応。日本語版の内容(デフォルトワークフロー指定・保存手順・有効/無効時の挙動・ブラウザ保存の旨)をそのまま英語・中国語に翻訳し、両ロケールのsettingsセクション末尾に追加。3言語とも見出し数が一致すること(settings: 5見出し、image-tab: 8見出し)を確認済み。

How to apply: comic-creator-workflowにある通り、ヘルプタブの英語・中国語訳は日本語版と完全に独立したデータで自動同期されない。見出し単位で丸ごと欠落することがあるため、関連機能のヘルプを触る際は都度、日本語版と同じ見出し数が3言語で揃っているか確認する。


2026-07-29(レイアウトタブの「I2I」「PI2I」ボタンを1つの「I2I」モーダルに統合)

「レイアウトタブのI2I、PI2Iを1つのモーダル(I2I)にして同様の内容で実行できるようにしたい」との依頼を受けて実装。直前に追加したImageタブ「Select I2I」パネル(本DEVLOGの直下エントリ参照)と同じ体験を、レイアウトタブでもモーダルとして提供する。

実装: 統合モーダル(static/js/main/15-pixifx-bridge.js openLayoutI2IModal()新規) templates/index.htmlのlayout-i2i-send-btn(I2I)・layout-pi2i-send-btn(PI2I)の2ボタンを、常時有効な単一のlayout-i2i-modal-btn(ラベル「I2I」)に統合。クリックで開くモーダルは、.tsm-overlay/.tsm-dialogパターン(09f-bubble-text.js等と同型)で実装し、Target(選択画像/ページ全体)トグル・Positive/Negative Prompt・Denoise・Run・I2I設定(デフォルトワークフロー使用チェック+ファイル名、14-integrations.jsのgetI2ISettingsState/saveI2ISettingsStateをそのまま使用しSelect I2Iパネルと共有)を持つ。実行は既存のsendI2IRunToWorkflowStudio()ブリッジをそのまま流用(Workflow Studio側の変更は不要)。既存sendSelectedImageToI2I()/sendCurrentPageToI2I()の画像Blob取得部分を_getSelectedImageBlob()/_getPageBlob()に切り出して共通化し、元の2関数は削除(sendImageToWorkflowStudioI2I自体はImageタブの「I2Iへ送る」ボタンが引き続き使うため残置)。04b-layer-panel-render.jsの旧ボタン無効化ロジック、16-processing-edit-tabs.jsのボタンイベント登録もあわせて更新。

実機フィードバックによる追加修正(4点)

  1. Layerモードと同じ「新規追加」方針への統一: 当初「選択画像」対象のRun結果はページ全体対象と同様に単純挿入する設計だったが、実装時点でレイアウトタブに「選択中の画像を置き換える」汎用関数が存在しないと判明。Select I2Iパネルでも直前のセッションで同様の理由から「新規レイヤー追加」に統一した経緯があったため、レイアウトタブも一貫して常に新規画像として挿入する設計で確定(ユーザー確認済み)。
  2. ページ全体対象の挿入先とサイズ: 実機確認後、「ページ全体の生成結果はオーバーレイに追加したい」「サイズが小さくなっている」の2点フィードバックを受けて対応。insertImageFromUrl()(08-panels-images.js)にplacement引数を追加(後方互換のため省略可)し、insertImage(dataUrl, imgW, imgH, {}, placement)へそのまま渡せるようにした。_pi2iResolvePagePixelSize()がSVG座標系(mm×100)でのページサイズsvgW/svgHも返すよう拡張し、_getPageBlob()の戻り値を{blob, pageW, pageH}に変更。ページ全体対象のRunでは実行直前にstate.selectedOverlay = trueをセットしてからinsertImageFromUrl(result.url, {x:0, y:0, width:pageW, height:pageH})を呼ぶことで、常にオーバーレイへページ全面サイズ(insertImageToOverlayの既定=挿入先の40%縮小を回避)で挿入されるようにした。
  3. 対象トグルの視認性: 「対象選択ボタンの選択状態がわかりにくい」というフィードバック。原因はCSSの設計自体にあった——.btn.activeという汎用ルールは存在せず、既存の.tmplwiz-orientation-buttons .btn.active/.btm-shape-btns .btn.activeのように親要素でスコープしたセレクタでのみ背景色が定義されている(activeクラス単体では見た目が変わらない)。同様に.btn.primaryという汎用ルールも存在しない。新規.li2i-target-btn.active { background-color: var(--primary-color); color: white; }をstyle.cssに追加して解決。
  4. レイアウト微調整: Positive/Negative textareaを3行→5行に、モーダル幅を900px→720px(.li2i-dialog、既定の.tsm-dialog比20%減)に、本文左右にpadding 24px(.li2i-body)を追加。Runボタンはmargin-left:autoでステータステキストと分離し行右端に配置。

検証: Kaptureで実機確認(15-pixifx-bridge.jsは非moduleのためwindow.関数名=...で直接上書きしリロード不要で検証)。選択画像対象・ページ全体対象の両方でRun→実際にWorkflow Studioで生成→結果が新規画像として正しい挿入先・サイズで反映されることを確認。

How to apply:

  • レイアウトタブに新しい.btnベースのトグルボタン群を追加する際、activeクラスをtoggleするだけでは見た目が変わらないことがある(既存の.btn.activeルールがすべて親要素スコープ付きのため)。新規トグルには対応するスコープ付きCSS(.独自クラス.active {...})を必ず追加すること。同様に.btn.primaryも見た目に影響しない可能性があるため、色を保証したい場合はスコープ付きCSSか既存の.btn.primary実装箇所を確認してから使う。
  • insertImage()系の関数は「コマ/オーバーレイ幅の40%」という既定サイズを前提にしている。ページ全体のI2I結果のような「対象そのものと同じサイズで挿入したい」ケースでは、placement引数を明示的に渡す必要がある(省略すると意図せず縮小される)。

2026-07-29(Imageタブ Selectツールに常時I2Iパネル「Select I2I」を新規追加、I2I設定を設定タブから移設)

「Imageタブのセレクトメニューもプロパティを常時表示、マスクメニューのinpaintのようにI2I用のPositive Prompt、Negative Prompt、Ksampler設定Denoise、Run、I2I設定(設定タブのI2I設定を移設)をしたい」との依頼を受けて実装。既存のMaskツール「Inpaint」サブツール(プロパティパネル常時表示+その場でWorkflow Studio実行→結果を新規レイヤーとして反映、という完成済みパターン)をマスク処理抜きで踏襲した。

実装1: comic-creator側 image-tab.js _setActiveTool()のプロパティペイン非表示条件(toolId !== "mask" && ... !== "fill")に&& toolId !== "select"を追加し、Selectツール選択中も#ie-props-paneが常時表示されるようにした。新規_renderSelectI2IProps()(_renderMaskProps("inpaint")と同型)で、Target(All/Layerトグル)・Positive/Negative Prompt・Denoise・Run・ステータス・I2I設定の各UIを#ie-props-bodyにinnerHTML生成する。実行本体_runSelectI2I()は、Allモードは_buildCompositeCanvas()(既存のInpaintと同じ合成)、Layerモードはthis._layerMgr.activeLayer.canvas(変形前の生コンテンツ)をPNG化してsendI2IRunToWorkflowStudio()へ渡す。結果の反映は当初「Layerモードは元レイヤーのcanvas内容を直接差し替え」で実装したが、実機確認後のフィードバックで「Layerモードも新規レイヤーとして追加したい」と修正依頼があり、新規_addI2IResultAsLayer()に差し替えた。元レイヤーと同じdisplayW/displayH/x/y/rotation/flipX/flipYを引き継いだ新規レイヤー(名前は<元レイヤー名> I2I)を追加し、元レイヤー自体は変更しない(Allモードは従来どおり_loadFromDataUrl(result.url, "I2I Result")で新規レイヤー化、これは無改造)。

実装2: I2I設定の移設 設定タブの「I2I設定」ブロック(templates/index.html、デフォルトワークフロー使用チェック+ファイル名)を削除し、Select I2Iパネル内に同等のUIを新設。14-integrations.jsのinitI2ISettings()(設定タブDOM前提の初期化)を削除し、代わりにwindow.getI2ISettingsState()/saveI2ISettingsState()という薄いgetter/setterに置き換えた。設定データ自体(_i2iSettings、localStorage ccc_i2i_settings)はレイアウトタブの既存I2I送信(sendImageToWorkflowStudioI2I)と共有のため変更していない。01-state.jsの設定タブ初期化からはinitI2ISettings()呼び出しを削除(initInpaintSettings()は据え置き、Inpaint設定は今回移設対象外)。

実装3: Workflow Studio側(別リポジトリ、新規ブリッジ) 既存のInpaint実行ブリッジ(gallery-tab.jsの_wfmReceiveInpaintRequest → image-edit-tab.jsのrunInpaintExternal/_runInpaintWithImages)を参考に、マスク不要のI2I版を新設。image-edit-tab.jsに_runI2IWithImage()(comfyEditor.applyImageToSlot()→setPromptText()×2→setInpaintParams({denoise})→window._wfmGenerateTab.generate()→結果URL取得)と外部エントリポイントrunI2IExternal()を追加。setInpaintParams()はgrowMaskBy未指定なら自動的にスキップされる既存実装だったため、denoise単体用の新規セッターは不要だった。gallery-tab.jsにwindow._wfmReceiveI2IRunRequestを追加し、comic-creator側からの呼び出しを受ける。既存の_wfmReceiveImageForI2I(画像を送るだけ、レイアウトタブのI2I/PI2Iボタンが使用)は無改造。変更後、開発リポジトリからcustom_nodes\comfyui-workflow-studio\(ComfyUI本体側、シンボリックリンクではなく通常コピー)へ手動反映。

検証: Kaptureで実機検証。window._ccImageTabのプロトタイプに新規メソッドを注入し、ページをリロードせずにUI・実行フロー(All/Layer両モードでの実際のI2I生成→新規レイヤー追加)を確認した。

ドキュメント: ヘルプタブ(_HELP_DATA日本語+_HELP_I18N.en/.zh)に新規見出し「Select I2I(Workflow Studio連携)」を追加し、既存の設定タブ・I2I連携(Workflow Studio)の解説もUI移設を反映。README(3言語)にも同内容を追記。あわせて、既存の「Inpaint(Workflow Studio連携)」見出しと設定タブ「Inpaint設定」見出しが英語・中国語訳に一件も存在しない(日本語版のみ)という既存の翻訳漏れを発見。Image タブの Inpaint 見出しは今回の作業ついでに英語・中国語へ追加したが、設定タブの Inpaint 設定見出しの翻訳漏れは今回のスコープ外として未対応のまま残した。

How to apply:

  • Workflow Studio連携で「その場で実行し結果を受け取る」機能(Inpaintパターン)を新設する際は、comfyEditor.applyImageToSlot/setPromptText/setInpaintParams/window._wfmGenerateTab.generateが既にマスクなし用途にも流用できる設計になっている(growMaskBy等のオプション引数は未指定なら書き込みをスキップする)。新規のI2I的な機能を追加するたびにWorkflow Studio側のプリミティブを増やす必要は薄い。
  • Kaptureでリロード禁止の検証を行う際、type="module"で読み込まれるクラス(window._ccImageTabのようにwindow公開されているインスタンス)は、Object.getPrototypeOf(instance)にメソッドを直接生やせば、ページをリロードせずに新規実装の動作確認ができる。iframe埋め込みの別アプリ(Workflow Studio)側の変更は、iframe.contentWindow.location.reload()でiframeだけ再読み込みすれば反映できる(ホストページ全体のリロードは不要)。
  • ヘルプタブの英語・中国語訳(_HELP_I18N)に日本語版と比べて見出し自体が丸ごと欠落していることがある(今回のInpaint関連2見出し)。新機能のドキュメントを書く前に、関連する既存見出しがそもそも3言語揃っているか確認する価値がある。

2026-07-28(ペイントツールに背景色指定・画像への直接描画・複数画像統合を追加、統合直後にドラッグできない不具合を修正)

前回追加したペイントツールの実機検証フィードバックを受けて3点追加、実装中に見つかったバグを1点修正した。

追加1: ペイントオブジェクト作成時の背景色指定(static/js/main/17d-layer-draw-paint.js _paintAddObject) 透過画像に描画した状態でI2I送信すると、多くの生成モデルが透過部分を黒として解釈してしまい意図しない結果になるとのフィードバックを受けて対応。「ペイントを追加」ボタン左隣に「背景色」チェックボックス+カラーピッカーを追加し、ONの場合はcanvasをctx.fillRect()で指定色に塗りつぶしてからtoDataURL()する(OFF時は従来通り全透過)。

追加2: 通常画像への直接ペイント(_paintMouseDown) これまではdata-ccc-paint-object="1"が付いたペイントオブジェクトにしかブラシ描画できなかったが、選択中の.inserted-image要素であれば通常画像(アセットから挿入した写真等)にも直接描き込めるよう判定条件を緩和した。

追加3: 複数画像の統合(Imageタブのレイヤー統合と同等機能)(新規_paintMergeSelected) I2Iは選択中の1オブジェクトしか送信できないため、複数レイヤーをまとめて送りたい場合の対応として実装。レイヤーパネルの既存チェックボックス機構(state.checkedLayerEls、グループ化「G+」と同じ仕組み)を再利用し、.inserted-image(画像・ペイントレイヤーいずれも対象)を2つ以上選択→「選択画像を統合」ボタンで合成する。同一コマ/オーバーレイ/下書き内であることを前提とし、各画像の絶対座標系での四隅(transformによる回転を考慮)からユニオンのバウンディングボックスを算出、選択画像のクローンを含む単独SVGを構築してconvertShapeToImage(09c-balloon-handles.js)と同じ手法でラスタライズ→1枚のPNGに合成する。元の画像は削除され、統合結果に置き換わる(重ね順は維持)。図形・テキストは「画像に変換」で画像化すれば統合対象にできるため、任意のオブジェクトをI2I用に柔軟に合成できる。

バグ修正: 統合直後に画像をドラッグ移動できないことがある _paintMergeSelectedは当初、DOM操作(元画像の削除+統合結果の挿入)とsavePanelSvgによる保存のみを行い、renderLayoutTab()(DOM全体の再構築)を呼んでいなかった。複製・移動など他の全てのDOM変更系処理は必ずrenderLayoutTab()で再構築しinitImageManipulation等のイベントリスナーを再バインドしているのに対し、統合処理だけこれを省略していたため、統合直後だけ稀にドラッグイベントの委譲が正しく効かなくなっていた(「別コマへ移動すると直る、元のコマへ戻しても直る」という報告から判明。移動処理は必ずrenderLayoutTab()を経由するため、それが暗黙の「直す」動作になっていた)。他の複製・移動系処理と同じパターン(DOM操作→保存→renderLayoutTab()→IDで再取得して再選択)に統一して解消。

How to apply: レイアウトタブでライブDOMを直接操作(要素の追加・削除・並べ替え)した後、その場で選択・ハンドル表示だけを更新してrenderLayoutTab()を省略すると、保存やレイヤーパネル表示は正しく動いても、ドラッグ等のインタラクションだけが不安定になることがある。既存の複製・移動処理が例外なく処理の最後にrenderLayoutTab()→IDで再取得→_selectClone()という手順を踏んでいるのは、この非対称性を避けるための一貫パターンである。新しくDOM変更系の機能を追加する際は、パフォーマンス上の理由がない限りこのパターンに従うこと。


2026-07-28(レイアウトタブに「ペイントツール」を新規追加、コマ間オブジェクト移動・保存の複数バグを修正)

「レイアウトタブにペイントツールを追加したい」との依頼を受けて実装。開発途中の実機検証で複数のバグ報告を受け、同セッション内で調査・修正した。

実装: ペイントツール(static/js/main/17d-layer-draw-paint.js 新規) 既存の「ドロー」(SVGベクター図形描画、17a〜17c)とは意図的に完全分離した独立サブタブとして実装(最初はドロー内の1シェイプ種別として実装したが、「既存ドローツールと分けたい」との要望を受けて独立タブへ再構成)。「ペイントを追加」ボタンで、選択中のコマ/オーバーレイ/下書きレイヤーのサイズにジャストフィットする透過PNG(<image data-ccc-paint-object="1">)を生成・挿入し、自動選択+描画ON状態にする。描画ON中はオーバーレイ用の透明canvasでマウスイベントを捕捉し、選択中のペイント画像のビットマップをオフスクリーンcanvasにデコードして丸ブラシで描画、都度hrefをtoDataURL()で更新する方式(ラスタライズ解像度は図形PNG変換と同じMAX_DIM=2000スケール)。ブラシサイズはスライダー化、消しゴム・x5(5倍サイズで描画、スライダー値自体は変更しない)はいずれもトグルボタン化しON/OFFを背景色で明示。レイヤーパネルではdata-ccc-paint-objectを見て🖌アイコン・「ペイント」名で通常画像と区別する。

バグ修正1: 削除済みオブジェクトがペイント追加時などに復活する savePanelSvg/saveOverlaySvg/saveDraftSvg/insertImage系/複製/移動はいずれも「state.activePageを読む→変更を反映→dbPut→state.activePageを更新後の値に差し替える」処理を個別に行っており、削除やペイント追加を短時間に連続実行すると、後発の呼び出しが先発の呼び出しのstate.activePage更新前に古い内容を読んでしまい、先発の変更(削除)が後発の保存で上書き消失していた。00-db.jsにグローバル直列化キュー_enqueueActivePageSaveを新設し、state.activePageを読み書きする8箇所全てをこのキュー経由に統一して解消。

バグ修正2: ドロー図形・ペイントオブジェクトを別コマへ移動するとバウンディングボックスが元の位置に残る/消える 3段階の不具合が重なっていた。(1) _applyCenterTranslate/_applyOffset(06a-polygon-geometry.js)が矩形・画像・テキスト以外の図形を「未分類」としてtransformの追加のみで動かしていたが、_drawShapeGetBounds(バウンディングボックス計算)は生のx/y・cx/cy・points等しか見ないため座標系がズレていた。図形種別ごとに正しい生座標を直接更新するよう修正。(2) _drawUpdateTransformForPathG(曲線・鎖・ロープ・My曲線・ベクター曲線のtransform再計算、17c-layer-draw-handles.js)がel.ownerSVGElementに依存しており、移動・複製処理でクローンをDOM未接続のまま座標更新するとnullになり関数が何もせず抜けていた(=data-x/data-yだけ新位置になり実描画位置は移動前のまま)。未接続時は使い捨ての<svg>要素で代替するよう修正。(3) moveSelectedObject/duplicateSelectedObject(05-groups-move.js)の移動元中心座標srcCx/srcCyがel.getBBox()(要素の生ジオメトリ、transform除外)から計算されていたため、一度でも移動・リサイズ済みのpath/g系図形では実際の現在位置とズレた中心を基準に移動先座標を計算してしまい、繰り返し移動すると誤差が蓄積してコマ外(クリップ範囲外)へはみ出し非表示になっていた。path/g系図形はdata-x/data-y/data-w/data-hから直接中心を計算するよう修正。Kaptureで実機検証し、移動後の中心座標が移動先コマの重心と一致することを確認済み。

ドキュメント: ヘルプタブ(日本語版_HELP_DATAおよび英語・中国語翻訳_HELP_I18N)とREADME(3言語)にペイントツールの説明を追加。あわせて、英語・中国語のヘルプ翻訳が以前から日本語版に対して欠落していた項目(サブコマ節・マンガサブタブ節が丸ごと未翻訳、下書きレイヤー・PI2Iの説明が欠落)に気づいたため、「レイアウト」セクション全体を日本語版と同じ17見出し構成へ揃えた。

How to apply:

  • SVGレイヤーへの描画機能で新しい図形種別・操作モードを追加する際は、_applyCenterTranslate/_applyOffset(コマ間移動・複製時の座標変換)が対象タグを正しく認識できるか必ず確認する。「未分類の図形」フォールバック(transformの追加のみ)は、独自の生座標(x/y以外の位置表現)を持つ要素には使えない。
  • el.ownerSVGElementはライブDOM接続時のみ値を持つため、クローン→座標更新→DOM挿入という順序の処理(複製・移動系)でSVGMatrixを使う関数を呼ぶ場合は、未接続でも動作するようフォールバック(使い捨ての<svg>要素等)を用意すること。
  • state.activePageを{...state.activePage, X: Y}の形で読み書きする処理が複数箇所に分散している設計では、短時間の連続操作(削除連打・複数追加など)で読み取りタイミングが競合し、片方の変更が失われるバグが起きやすい。同種の処理を追加する際は、既存の_enqueueActivePageSaveキューに必ず乗せること。
  • ヘルプタブの英語・中国語訳(_HELP_I18N)は日本語版(_HELP_DATA)と完全に独立したデータであり、自動同期されない。日本語のヘルプを更新した際は、同じidの英語・中国語エントリも忘れずに更新する(comic-creator-workflowのヘルプ更新手順に「3言語とも」の確認を追記する価値がある)。

2026-07-27(本セッションで追加した5機能をヘルプ・READMEへ反映)

「ヘルプを更新してください」「README、DEVLOGを更新してください」との依頼を受け、本セッションで実装した以下5点をアプリ内ヘルプタブ(static/js/main/22-help-tab.js、日本語のみ・全言語共通表示)とREADME(README.md/README_en.md/README_zh.mdの3言語)に反映した。

  • サブコマのレイヤーパネルからの複製・移動(オーバーレイへの移動含む)
  • アセットパネル「テンプレート」タブのグループ折りたたみ表示
  • Imageタブ Selectツールの「✂ クロップ」機能
  • Imageタブのレイヤーグループ(📁)機能とレイヤーパネル上部メニューの2行化
  • レイアウトタブの「PI2I」(ページ全体をPNGでWorkflow Studioへ送信)機能

ヘルプタブ側は、レイアウトタブの既存「I2I連携(Workflow Studio)」の解説と設定タブの既存「I2I設定」の解説を相互参照させ、デフォルトワークフロー自動読み込み設定が「I2I」「PI2I」の両ボタンに共通で効くことを明記した。

How to apply: ヘルプタブのデータ(_HELP_DATA)は日本語のみの単一配列で、UIの表示言語設定に関わらずヘルプ内容は常に日本語表示になる(i18n.jsの3言語辞書とは別系統)。機能追加時にヘルプへ反映する際は、この配列内の該当セクション(idとheadingで特定)を探して追記すればよく、i18n側の翻訳作業は不要。README側は3ファイルとも同じ見出し構成で並行しているため、1機能につき3ファイルへ同内容を翻訳して反映する。


2026-07-27(レイアウトタブに「PI2I」機能を新規追加、ページ全体をWorkflow StudioのI2Iへ送信)

「ページI2I機能(PI2I)を追加したい。ページとしてPNGで出力、WorkflowStudioに送ります」との依頼を受けて実装。

既存の「I2Iへ送る」ボタン(sendSelectedImageToI2I、static/js/main/15-pixifx-bridge.js)は選択中の<image>要素1枚をBlob化して送るだけの単一画像版だったが、送信先のsendImageToWorkflowStudioI2I(blob, name, sourceTab)(14-integrations.js)自体は任意のBlobを受け取れる汎用実装で、state.selectedImageElには一切依存していなかったため、ページ全体のPNG化さえ用意すれば流用できることが分かった。ページ全体のPNG化も、既存のPDF/EPUB/PNG出力(12-text-png-export.jsのhandleExport())が使っている経路(buildMergedSvg→embedFontsInSvg→drawSvgOnCanvas→共有#render-canvasのcanvas.toBlob)をそのまま流用できたため、新規のPNG化ロジックは書かず、既存の出力パイプラインに乗せた。

実装:

  • static/js/main/15-pixifx-bridge.js: 新規sendCurrentPageToI2I()。state.activePageからdbGet('pages', ...)でpageRecordを取得→buildMergedSvg(pageRecord)(既存出力と同じくオプション無し=下書きレイヤーは含めない)→embedFontsInSvg→共有#render-canvasに白背景でdrawSvgOnCanvas→canvas.toBlob('image/png')→sendImageToWorkflowStudioI2I(blob, pageRecord.name, 'layout')という一連の流れ。単一画像版のsendSelectedImageToI2Iと同じエラーハンドリング(layout.msgWfmI2ISendFailed)に合わせた。
  • templates/index.html: レイアウトタブの上部メニューに「PI2I」ボタン(#layout-pi2i-send-btn)を「I2I」ボタンの直後に追加。画像選択に依存しないため(既存の「I2I」ボタンと異なり)常時有効。
  • static/js/main/16-processing-edit-tabs.js: クリックハンドラをsendSelectedImageToI2Iの登録直後に追加。
  • static/js/i18n.js: layout.sendToPi2iBtn/sendToPi2iTitle/msgNoActivePageをja/en/zhに追加(msgNoActivePageはページ未オープン時のガード用。既存のmsgSelectImageFirstは文言が「画像を選択」に固定されておりページ未オープンの文脈と合わないため、専用キーを新設した)。

How to apply: 「単一オブジェクトを外部連携へ送る」機能(今回のI2I)を「ページ全体」等の広い単位に拡張したい場合、まず送信先の橋渡し関数(sendImageToWorkflowStudioI2I等)が本当に対象オブジェクト固有の状態(state.selectedImageEl等)に依存しているか確認するとよい。多くの場合、橋渡し関数自体は「Blobを受け取って送るだけ」の汎用実装になっており、新しく用意すべきは「対象をBlobに変換する経路」だけで済む。今回のようにその変換経路(SVG→PNG)が別の既存機能(PDF/EPUB/PNG出力)で既に確立済みなら、それをそのまま呼び出すのが最小実装になる。

追記(同日、実機検証後): 実機で「Workflow Studioへの送信に失敗しました: Blob生成失敗」が発生(ComfyUI再起動後も再現、ワークフロー自体は生成可能とのことでcanvas側の問題と判断)。原因はcanvas.width = pageRecord.width; canvas.height = pageRecord.height;で、pageRecord.width/heightはピクセル数ではなく**SVG座標単位(mm×100相当。A4なら21000×29700)**で保持されている値だったこと。これをそのままcanvasのピクセルサイズに使うと約6億2000万ピクセル相当の巨大canvasになり、canvas.toBlob()が(例外を投げず)コールバックにnullを返してpage.errBlobGenFailedに落ちていた。出力タブの解像度自動計算(_applyExportDpi、10-output-pages.js)が同じmm×100値をmm = 値/100→px = mm * dpi / 25.4で変換し_EXPORT_MAX_SIZE(8000px)でクランプしているのと同じ考え方を、新設の_pi2iResolvePagePixelSize(pageRecord)として15-pixifx-bridge.js内に実装(DPIは固定150、_EXPORT_MAX_SIZEは出力タブの定数をそのまま流用)。pageRecord.width/heightが無い/不正な場合はsvgContentのviewBoxからもフォールバック取得するようにした。

How to apply(追記): このアプリでは「作品/ページのwidth・height」はどこで保持されていても一貫して**SVG座標単位(mm×100)**であり、ピクセル値ではない(WORK_SIZE_PRESETSのA4が{width:21000, height:29700}であることからも分かる)。新しくcanvasへラスタライズする処理を書く際は、既存の出力コード(12-text-png-export.js/10-output-pages.js)のように必ずDPI等でpx換算してからcanvas.width/heightに設定すること。巨大canvasでの失敗は例外にならずtoBlob/toDataURLがnullを返すだけなので、開発中に気づきにくい。


2026-07-27(Imageタブにレイヤーグループ機能を追加、レイヤーパネル上部メニューを2行化)

「レイヤーグループを可能にしたい。レイヤー上部メニューを2行にして下段にグループと統合ボタンにしてください」との依頼を受けて実装。

ImageタブのレイヤーモデルはLayoutタブ(SVG、state.activePage.panels[])とは全く別物で、LayerManager.layersが実ピクセルを持つLayerオブジェクトのフラット配列(composite()が配列順そのまま前面→背面の合成順として扱う)だったため、グループを配列内の疑似レイヤー(type:"group"等)として実装するとcomposite/mergeLayers/toJSON/fromJSONなど複数箇所に分岐追加が必要になり影響範囲が大きくなる。そこで、各LayerにgroupIdフィールドを追加し、LayerManager.groups({id, name, collapsed}の配列、メンバーはlayer.groupIdで判定)という配列の外側にある並行レジストリとして実装した。これによりlayers配列自体の並び(=実際の合成順)には一切手を加えず、レイヤーパネルの表示だけがグループヘッダー配下にメンバーをまとめて描画する(Layoutタブのサブコマで使った「データの配列順は変えず表示だけ並べ替える」パターンと同じ考え方)。

実装:

  • static/js/image-tab/LayerManager.js: LayerにgroupId(デフォルトnull)を追加しtoJSON/fromJSONにも反映。LayerManagerにgroups配列を追加し、createGroup(layerIds, name)(2枚以上選択時のみグループ化、既に別グループにいた場合はそちらから抜ける)・ungroup(groupId)・toggleGroupCollapsed(groupId)・setGroupVisible(groupId, visible)・_pruneEmptyGroups()(メンバー0枚になったグループ定義を自動掃除、deleteLayer/mergeLayers/createGroup後に呼ぶ)を新設。mergeLayers()は統合対象が全て同一グループのメンバーだった場合のみ統合結果もそのグループに残す。duplicateLayer()も複製後に元と同じグループへ残すようgroupIdをコピー。LayerManager.toJSON/fromJSON(マネージャー単位)にもgroupsを追加し、Undo/Redoでグループごと復元できるようにした。
  • static/js/image-tab.js: _refreshLayerList()を、layers配列の並び順を保ったままgroupIdでメンバーをまとめてグループヘッダー(📁アイコン・件数・一括表示/非表示ボタン・解除ボタン、クリックで折りたたみ)配下に描画するよう変更(_renderLayerRowHtml/_renderGroupHeaderHtmlに処理を切り出し)。新規_groupSelectedLayers()を追加し、レイヤーパネルの既存の複数選択(Shift+クリック、_selectedLayerIds)をそのまま「グループ化するレイヤーの選択」として流用した。
  • templates/index.html / static/css/image-tab.css: レイヤーパネル上部の.ie-layers-header(1行)を2行に分割し、1行目に既存のAdd/Mask追加/削除/上へ/下への各ボタン、新設の2行目(.ie-layers-header-row2)に「📁 グループ」ボタンと、既存の「統合」ボタン(#ie-flatten-btn、元々1行目末尾にあったものを移動)を配置。
  • static/js/i18n.js: image.group/groupTitle/groupSelectFirst/groupDone/ungroup/ungroupTitleをja/en/zhに追加。

「統合」ボタン自体は今回新規実装したものではなく、既存のmergeLayers()(選択レイヤーのみ統合、2026-07-24のInpaint機能追加時にマスクレイヤー対応で修正済み)に繋がる既存ボタンをレイアウト変更に伴い2行目へ移設しただけである。

How to apply: 既存のデータモデル(今回で言うLayerManager.layersの「配列順=実際の描画/合成順」という不変条件)を壊さずに新しい「グルーピング」概念を追加したい場合、対象の配列に疑似要素を混ぜ込むより、外側の並行レジストリ(id参照ベースのメンバーシップ)として実装する方が、既存の合成・シリアライズ・統合ロジックへの影響を最小化できる。表示順の組み替えが必要な場合も、実データの並びには手を付けず「レンダリング時にメンバーをまとめて描画する」だけで対応できる(comic-creator-workflowのサブコマ表示順対応と同じ考え方)。


2026-07-27(ImageタブのSelectメニューに「クロップ」ツールを新規追加)

「Selectメニューにクロップボタンを追加。オーバーレイのクロップエリアをサイズ変更し実行としたい。メニューにはx,yでサイズしても可能にしたい」との依頼を受けて実装。

Imageタブのキャンバス/レイヤーモデルを調査したところ、各レイヤーは自身のネイティブ解像度の独立した<canvas>(layer.canvas)を持ち、ページ全体上の配置はlayer.x/y(絶対座標)で表現されている(LayerManager.compositeがlayer.x/y起点で合成し、ページキャンバス境界外はそのまま描画クリップされる仕組み)ことが分かった。このため「クロップ」は各レイヤーのピクセルデータを一切書き換えず、①_canvasW/_canvasH(ページ全体サイズ)とクロップ後サイズへ縮小、②全レイヤーのx/yをクロップ原点分シフト、③#ie-canvas-draw/#ie-canvas-overlay/#ie-canvas-containerのリサイズ、の3点だけで実現できる設計にした。

実装:

  • static/js/image-tab.js: Selectツールのオプションパネル(_renderToolOptionsのtoolId === "select"分岐)に「✂ クロップ」トグルボタンを追加。ONにすると#ie-canvas-overlay上にドラッグ可能なクロップ範囲(半透明の範囲外オーバーレイ+点線枠+四隅/四辺中点の8ハンドル、_drawCropOverlay/_cropGetHandlePositions/_cropHitHandle)を表示し、同時にX/Y/W/H数値入力欄も表示してドラッグ・数値入力のどちらでも範囲を調整できるようにした(_onCropMouseDown/_onCropMouseMoveをSelectツールのマウスディスパッチ内に分岐追加)。「実行」ボタンで_applyCrop()を呼び、上記3点の変更を適用する。「キャンセル」またはクロップボタン再クリックで_exitCropMode()により編集前の状態のまま抜けられる。
  • _initCanvases()内のキャンバス要素リサイズ処理(ie-canvas-draw/ie-canvas-overlay/ie-canvas-containerのwidth/height設定)を_resizeCanvasElements(w, h)として切り出し、新規キャンバス作成時とクロップ実行時の両方から共通で呼べるようにした(LayerManagerの再構築は伴わない、既存レイヤーを維持したままのリサイズ用)。
  • static/js/image-tab/LayerManager.js: fromJSON()がtoJSON()で保存していたはずのwidth/heightを復元していなかった(従来はキャンバスサイズを変える操作自体が存在せず、Undo/Redoで顕在化していなかった潜在バグ)ため修正。あわせてimage-tab.jsの_restoreState()(Undo/Redo共通処理)で、復元後のlayerMgr.width/heightが現在の_canvasW/_canvasHと食い違っていれば_resizeCanvasElementsで同期するようにし、クロップ操作もUndo/Redoで正しくキャンバスサイズごと戻る/やり直せるようにした。
  • static/js/i18n.js: image.cropBtn/cropBtnTitle/cropXLabel/cropYLabel/cropWidthLabel/cropHeightLabel/cropApply/cropCancelをja/en/zhに追加。

How to apply: Imageタブのようにレイヤーがページ座標系へ絶対配置される合成モデル(layer.x/y + composite時にキャンバス境界でクリップ)を持つエディタでキャンバスサイズを変更する機能を作る場合、レイヤーのピクセルバッファ自体を再サンプリングする必要はなく、「ページサイズを変える」+「全レイヤーのオフセットをシフトする」の2点で足りる。またtoJSONが保存している値は将来的に何かの操作がその値を変更する可能性がある以上、対応するfromJSON側で必ず復元しておかないと、Undo/Redoが暗黙に「その値は不変」という前提に依存してしまい、後から来た機能(今回のクロップ)が原因不明の状態不整合を起こす。


2026-07-27(アセットパネルのテンプレート"T"タブをグループ折りたたみ表示に対応)

「テンプレート数が増えることでサムネが小さくなりすぎる対策とグループ表示による素早い選択を兼ねたい」との依頼を受けて実装。対象範囲(テンプレートTタブのみ/ページPタブは対象外)とグループ分けの基準(既存のテンプレートグループ機能を流用)は質問して確認した。

「ページ」タブ→「テンプレート」サブタブ側に既にグループ管理機能(_tmplGroups、06b-template-manager.js。グループの作成・削除・リネームとテンプレートの割り当てをlocalStorage template_groups に永続化)があったため、新しいデータ層は作らず、アセットパネル側の表示だけをこのデータを読んで組み替える形にした。

実装:

  • static/js/main/11a-work-manager.js: renderAssetTemplateGrid()を、テンプレートを_tmplGroups.groupOf()でグループ別に振り分けてから描画するよう変更。グループは.asset-folder(アセットツリーの折りたたみフォルダと同じCSSクラス、static/css/style.css)のヘッダー行+件数バッジで表示し、クリックで展開/折りたたみを切り替える。展開状態はモジュールスコープの_assetTmplExpandedGroups(Set)に保持し、初期状態(未展開)は空集合=全グループ折りたたみになる。グループ未所属のテンプレートは従来通りヘッダー無しでそのまま並べるため、グループ機能を使っていないユーザーには見た目の変化がない。空グループ(所属テンプレート0件)は表示しない。
  • static/css/style.css: グループ内カード列用の.asset-tmpl-group-list(.asset-listと同じ考え方で.collapsed時display:none)と件数バッジ.asset-tmpl-group-countを追加。

How to apply: 同じ「グループ分け」概念を複数のUI(今回は「ページ」タブのテンプレート管理と、アセットパネルのテンプレートタブ)で使う場合、データ層(_tmplGroups等)を一本化して片方は表示専用にすると、グループの作成・削除・リネーム・割り当てロジックを二重実装せずに済む。折りたたみUIは.asset-folder/.asset-list(アセットツリーで既に確立済みのクラス・挙動)を流用すると、新規CSSは「グループ内リストの入れ物」用の最小限で足りる。


2026-07-27(サブコマをレイヤーパネル下部の「移動」「複製」ボタンに対応)

「前回追加したサブコマについてレイヤーパネル下部のレイヤー移動、複製は可能ですか?」との質問を受けて調査したところ、下部の移動/複製ボタン(duplicateSelectedObject/moveSelectedObject、05-groups-move.js)はstate.selectedGroupId/selectedShapeId/selectedImageEl/selectedTextEl/selectedDrawIdのいずれかしか見ておらず、サブコマ選択は通常のコマ選択と同じstate.selectedPanelIdを使う仕組みのため対象外だった(押しても「対象を選択してください」のアラートが出るだけで何も起きない)ことが判明。「実装願います」との依頼を受けて対応した。

実装:

  • static/js/main/24-sub-panels.js: インタラクティブなドラッグ移動用にinitSubPanelManipulationのクロージャ内に閉じていた_subPanelSnapshotContent/_subPanelApplyContentTranslate(中身のオブジェクトを種別ごとの正規座標属性で平行移動するロジック)をトップレベル関数に切り出し、新規のduplicateSubPanel(subId, targetParentId)/moveSubPanel(subId, targetParentId)から共通利用できるようにした。
    • duplicateSubPanel: 対象未指定(=同じ親コマ)ならオブジェクト複製と同じOFFSET(20,20)でずらして複製、異なる親コマを指定した場合は複製元/複製先の親コマ中心の差分だけ平行移動。枠線・中身ともに_cloneWithNewIds(グループ複製で使用の既存ヘルパー)でID一括付け替えし、新規panel-clip-<newId>クリップパスを複製先親コマとの交差ポリゴン(既存の_subPanelEffectiveClipPoints)で作成。
    • moveSubPanel: 同じidのままparentPanelIdを付け替え、複製元/複製先の親コマ中心の差分だけ枠線・クリップ・中身を平行移動。自分自身や自分の子孫サブコマを移動先に選ぶ循環参照は_subPanelIsDescendantOfでガード。
    • どちらも通常のコマ・パネルSVG永続化経路(savePanelSvg)をそのまま利用(state.activePage.panelsにエントリを追加/更新後、savePanelSvgが内部で対応エントリを見つけてDOM内容を書き戻し+dbPutする既存動作に乗せている)。
  • static/js/main/05-groups-move.js: duplicateSelectedObject/moveSelectedObjectが対象オブジェクトなし判定した直後に、選択中が_subPanelCurrentSelected()(既存ヘルパー)で取得できるサブコマかどうかをチェックし、該当すればduplicateSubPanel/moveSubPanelに委譲する分岐を追加。既存のレイヤーパネル下部UI(移動先コマ選択ドロップダウン・移動/複製ボタン)をそのまま流用できる。
  • i18nメッセージは既存のlayer.duplicateTargetPanelNotFound等(汎用的な文言のため)をそのまま流用し、新規キー追加はなし。

How to apply: 「コマ」抽象を流用して作られた新しいエントリ種別(comic-creator-workflowのサブコマのような)にレイヤーパネルの既存の移動/複製ボタンを対応させる場合、対象特定ロジックがチェックするstate.selected*変数の一覧に新しい選択状態判定を追加するだけで、UI(ドロップダウン・ボタン)は無改造のまま流用できる。中身のオブジェクトを種別ごとに正しく追従移動させるロジック(_subPanelApplyContentTranslate等)はクロージャ内に一度書くと使い回しづらくなるため、インタラクティブ操作専用に見えても最初からトップレベル関数として書いておくと、後から「同じ移動をプログラム的にも呼びたい」という要望に対応しやすい。

追記(同日、実機検証後): 上記の移動/複製を実機確認した後、「オーバーレイへも可能にしたい」との追加依頼を受けて対応した。オーバーレイはstate.activePage.panels[]の実エントリではない('__overlay__'という特別なID文字列で扱われる)ため、サブコマのparentPanelIdにそのまま'__overlay__'を許すと、①_subPanelEffectiveClipPoints等の「親コマのpointsで交差クリップ」計算がstate.activePage.panels.find(...)で親が見つからず素通り(無クリップ)になる、②レイヤーパネルの表示ロジック(orderedPanelsはトップレベルコマからparentPanelIdを辿って子を並べる仕組みのため、親が実在しない'__overlay__'だとどこからも辿り着けず表示から消える)という2つの穴があった。

  • 24-sub-panels.js: _subPanelResolveParent(parentPanelId)を新設し、'__overlay__'の場合はstate.activePage.basePanelPoints(ページ全面の形)を疑似的な親として返すようにした。_subPanelCommit(ドラッグ確定時のクリップ再計算)・duplicateSubPanel・moveSubPanelの親コマ解決をすべてこれ経由に統一し、通常のオブジェクトがオーバーレイへ移動する際にbasePanelPointsの重心へ再配置されるのと同じ考え方で中心合わせできるようにした。
  • 04b-layer-panel-render.js: コマ1件分の行+マスク行+中身オブジェクト一覧を描画する処理をrenderPanelNode関数として切り出し、通常のコマ一覧ループに加えて、オーバーレイ行の直後でもpanels.filter(p => p.parentPanelId === '__overlay__')分を呼べるようにした(オーバーレイもコマ一括ロックの対象外という既存仕様に合わせ、呼び出し後に_rlpPanelLockedをリセットする処理も追加)。表示名はparentPanelId === '__overlay__'のときだけ専用ラベル(subpanel.optionLabelOverlay)を出すよう分岐。
  • i18n.js: subpanel.optionLabelOverlayをja/en/zhに追加。

移動先コマ選択ドロップダウン自体は既存のupdateDuplicatePanelSelect(03-layers-panel.js)がもともと__overlay__オプションを無条件で持っていたため、UI側の変更は不要だった。

How to apply: 「実コマの集合(panels[])」と「オーバーレイ(__overlay__という特別なID)」のように、同じ「親」概念を扱う先が「配列内の実エントリ」と「配列外の特別な存在」に分かれている場合、.find(p => p.id === X)をそのまま使い回すコードは後者で静かにundefinedを返して意図しない動作(クリップ無効化・表示から消失等)になりやすい。X === '__overlay__'等の特別値を吸収して両者を同じインターフェース(今回は{points}を持つオブジェクト)に正規化する解決関数を1つ作り、すべての参照箇所をそれ経由に統一するとこの種の穴を防ぎやすい。


2026-07-27(レイアウトタブに「サブコマ」機能を新規追加、実機検証で見つかった不具合を順次修正)

「レイアウトタブでコマの中にコマをオブジェクトとして入れたい。矩形、丸でサイズ変更、移動を可能としたい」との依頼を受けて実装。続けてユーザー自身の実機検証で複数の不具合・追加要望(回転対応、コマ外へのはみ出し、サブコマ自体と中のオブジェクトの操作の切り分け、枠線幅の個別設定、上部メニューのレイアウト整理)が見つかり、同じ流れで対応した。

サブコマは「state.activePage.panels[] の1エントリ(id + points)」という既存の抽象をparentPanelId付きで拡張しただけの設計とし、insertImage/getOrCreateClipGroup/savePanelSvg/buildMergedSvg/selectPanel/initPanelsOnSvgのクリック選択・パネルロック/マスク機能をほぼ無改造のまま流用できるようにした(新規ファイルstatic/js/main/24-sub-panels.js)。

実装:

  • 新規作成: ツールペイン「サブコマ」サブタブでON+矩形/丸を選び、実際にドラッグを開始した座標がどのコマ形状の内側にあるかをレイキャスト法の点内判定(_subPanelFindPanelAtPoint)でその場で検出し、その場所を含むコマを親として自動決定する(あらかじめ「コマ:」を選択しておく必要はない。選択中コマを使う実装だと、選択がズレていた場合に見た目と違うコマの子として作成されてしまう不具合になった)。
  • 移動・リサイズ・回転: 17c-layer-draw-handles.jsのdraw-shape用ロジック(_drawShapeGetBounds/_drawShapeSetBounds/_drawShapeApplyRotation/_polygonBakeRotation等)をそのまま流用。
  • 親コマ外へのはみ出し: 当初は位置をコマ内に強制的にクランプしていたが、「通常のオブジェクトのようにコマ外へ動かせ、はみ出た部分をクロップする挙動にしたい」というフィードバックを受け、Sutherland-Hodgman法によるポリゴン交差計算(_subPanelClipPolygon)に変更。サブコマ自身の形と親コマの形の交差部分だけを実際のclipPathに使い、枠線・ハンドル・保存データは常にサブコマ本来の完全な形(はみ出し込み)を保持する。
  • サブコマ移動時の中身追従: 「サブコマ自体を動かしたい=中のオブジェクトもそのまま動いてほしい」というフィードバックを受けて対応。moveSelectedObjectToCenter(15-pixifx-bridge.js)と同じ「要素種別ごとに本来の位置属性(画像=x/y、テキスト=x/y+tspan、フキダシ=dataset.cx/cy、グループ=data-tx/ty)へ直接座標を書き込む」方式を採用した。transformを重ねるだけの簡易実装も試したが、画像等の選択ハンドル表示ロジックがtransformを見ずx/y等の生属性だけを見て動くため、移動後に中のオブジェクトを選択するとハンドルが移動前の位置に残ってしまう不具合になった。
  • 既存オブジェクトとの操作衝突防止: サブコマ作成ドラッグのmousedownハンドラを、画像/テキスト/フキダシ/ドロー図形/グループの各操作ハンドラより先に発火させるため、キャプチャフェーズでの先取り登録(06a-polygon-geometry.jsのinitGroupManipulationと同じ手法)に変更。バブルフェーズのままだと、コマ内の画像等の上からサブコマを描いた際に両方のハンドラが反応し、画像が一時的に引きずられてから元の位置に戻る不具合になっていた。
  • 枠線のクリック挙動: 枠線ポリゴンのpointer-eventsをstrokeに限定(allのままだと塗り全体(透明部分含む)がクリックを捕捉し、下に敷いた画像へのドラッグを奪ってしまう不具合があった)。
  • サブコマ操作の切り替え: 当初はツールペインにグローバルなON/OFFトグルを設けたが、「レイヤーパネルでサブコマごとに切り替えたい」というフィードバックを受け、レイヤーパネルのサブコマ行アイコンをチェックボックスに変更。ON時はそのサブコマ内クリックで中のオブジェクトより優先してサブコマ自体を選択・移動し、OFF(既定)では中のオブジェクトを個別操作できる。状態はパネルロックと同じくg[data-clip-panel]のdata属性として持たせ、既存の保存経路でそのまま永続化されるようにした。
  • 枠線幅の個別設定: 「サブコマ」サブタブに選択中サブコマの枠線幅入力欄を追加。他のサブコマ・コマの枠線幅とは独立して設定できる。
  • レイヤーパネルの表示順: サブコマは配列末尾に追加される仕様のため、そのまま並べると実際の親コマと無関係に「配列で直前にあるコマの子」に見えてしまっていた(データは正しいが表示が紛らわしい不具合)。表示専用の並び替えを追加し、実際の親コマの直後に子サブコマが並ぶよう修正。
  • 上部メニューのレイアウト整理: 折り返して2行になっていたボタン列を1行に収めるため、コマ枠線幅入力欄の縮小、「I2Iへ送る」→「I2I」・「作品を閉じる」→「閉じる」のラベル短縮を実施。続けて「コマ:」ドロップダウン・「削除」「元に戻す」ボタンの並び順をユーザー指定の位置(元に戻すの右隣/画像を挿入の左隣)に整理し、単独行になった「保存」ボタンもページ送り行へ統合した。

検証(Kapture、実機http://127.0.0.1:8189/ccc): 作成・選択・リサイズ・回転・親コマ外へのはみ出し時のクロップ・枠線幅変更・移動時の中身追従・レイヤーパネルのチェックボックスの表示切替・上部メニューの1行化のいずれもコンソールエラーなしで確認。ドラッグ操作そのもの(mousedown→mousemove→mouseup)は現行のKaptureツールセットでは個別イベントとして直接シミュレートできないため、コードレビューと部分的なクリック確認を組み合わせつつ、実際のドラッグ挙動の最終確認はユーザー自身の実機検証に委ねた(4件の不具合はすべてユーザーからのフィードバックで発見・修正)。

How to apply: 新しい「コマ的なオブジェクト」を追加する際は、state.activePage.panels[]にparentPanelId付きの通常エントリとして追加する設計にすると、既存のパネル関連インフラ(挿入・保存・選択・ロック・マスク)を無改造で流用できる。オブジェクトを他のオブジェクトごと移動する新機能を実装する際、transformを重ねるだけの簡易実装は既存の各オブジェクト種別の選択ハンドル表示ロジック(x/y等の生属性だけを見るものが多い)と食い違うため、moveSelectedObjectToCenterと同じ「種別ごとに本来の位置属性へ直接書き込む」方式に統一すること。また同じsvgElに複数のmousedownハンドラが積み重なっている場合、新しい操作を既存ハンドラより優先させたいときはキャプチャフェーズ登録(addEventListener第3引数true)で処理後にstopPropagationすると、バブルフェーズの既存ハンドラとの二重発火を避けられる。


2026-07-24(背景パターンのカスタムSVGアップロードをセキュリティレビュー)

v1.13.0リリース後、ユーザーから「カスタムSVG読み込みについてセキュリティチェックをお願いできますか」との依頼を受けて15c-manga-bgpattern.jsのカスタムSVGアップロード経路(_mangaBgRecolorSvgString/_mangaBgLoadSvgImage/_mangaBgBuildSvgTileCanvas)をレビューした。

アップロードされたSVGはnew Image(); img.src = "data:image/svg+xml,..."という<img>要素のコンテキストで読み込まれており、ブラウザ仕様上この経路では内部の<script>要素やイベントハンドラ(onload等)は実行されない(innerHTMLでのDOM直接挿入や<object>/<iframe>埋め込みとは異なる安全な経路)。読み込んだ画像はctx.drawImage()でcanvasに描画され最終的にPNGとしてラスタライズされるため、元のSVGコードはどこにも保存・送信されない。色置換で注入する<style>ブロックの値は<input type="color">由来でブラウザが常に#rrggbbに正規化するため文字列注入の余地もなく、外部リソース参照を含むSVGによるcanvas.toDataURL()のcanvas tainting(SecurityError)も呼び出し元のtry/catchで握られており実害はない。重大な脆弱性は見つからなかった。

レビュー中に見つけた軽微な不具合(セキュリティとは無関係)として、renderPreview()内の_mangaBgRenderPatternToCanvas呼び出しがtry/catchで囲まれておらず、画像として解釈できない壊れたSVGファイルをアップロードすると未処理のPromise rejectionがコンソールエラーになる点を修正した。

実装:

  • static/js/main/15c-manga-bgpattern.js: renderPreview()内のパターン描画をtry/catchで囲み、失敗時はconsole.errorでログするだけで操作を継続できるようにした。

How to apply: ユーザーアップロードのSVGをブラウザ内で画像として扱う場合、<img>要素(srcに直接ファイルURLまたはdata: URIを設定)またはCSS background-image経由でレンダリングする限り、内部のスクリプトは実行されない。逆にinnerHTMLでDOMに直接挿入したり<object>/<iframe>で開いたりする実装に変更する場合は、この前提が崩れるため別途サニタイズ(<script>除去等)が必須になる。


2026-07-24(マンガツールに「背景パターン」を新規追加、デモ画像・README更新)

「レイアウトタブに新しい機能、背景パターンを追加したい。マンガツール内に"背景パターン"ボタンを追加。選択中のコマ、またはオーバーレイのサイズにパターンを作成する機能のモーダルを表示します。参考としてworkflow studioの設定、テーマのカスタマイズの背景パターンをベースに機能を拡張したい」との依頼を受けて実装。続けてユーザーからのフィードバック(パラメータに回転・カスタムSVGの縦横個別サイズを追加、モーダルサイズ拡大でスクロール不要に)を反映し、4機能(PixiJS FX・ハーフトーン・マンガ効果・背景パターン)のデモ画像撮影とREADME 3言語への反映まで一連の流れで対応した。

参考にしたComfyUI-Workflow-Studio(comfyUI-wf-maneger)の「テーマのカスタマイズ」→「背景パターン」は、ストライプ×3・ドット・チェック・カスタムSVGアップロードをCSSのbackground-imageでページ全体に敷き詰める実装。一方Comic Creator側には既に「マンガ効果」(15b-manga-tone.js)という、選択中のコマ/オーバーレイのサイズにジャストフィットする透過PNG画像を生成・挿入する仕組みが確立していたため、その基盤(対象領域決定・キャンバスサイズ計算・プレビュー背景ガイド切替・挿入処理)をそのまま再利用し、Workflow Studio実装をCanvas 2D移植する形で新規モーダルとして実装した。ユーザーの選択(PNG画像挿入方式・カスタムSVGアップロード対応・和柄パターン追加)に基づき、Workflow Studioにはない和柄4種(麻の葉・市松・七宝・鱗)も追加している。

実装:

  • static/js/main/15c-manga-bgpattern.js(新規): パターン10種(横縞/縦縞/斜め縞/ドット/チェック/麻の葉/市松/七宝/鱗/カスタムSVG)のCanvas 2Dタイル描画関数。いずれもタイル境界をまたいでも継ぎ目なく繰り返せるよう座標計算(斜め縞はx-y=kの45度直線群をkをタイルサイズ分ずらして複数描画、麻の葉は六角格子(pointy-top)の中心から6頂点への放射線を行ごとに半分ずらして配置、七宝は円弧をタイルの四隅・辺の中点・中心に規則正しく配置)。パターン全体の回転は個々のタイル描画ロジックを変えずpattern.setTransform(new DOMMatrix().rotate(angle))で実現(CanvasPatternは無限に繰り返される平面なので回転の中心や境界の継ぎ目を気にする必要がない)。カスタムSVGはWorkflow Studioの色置換ロジック(fill/stroke属性・インラインstyle・<style>ブロック注入)を移植し、幅・高さを独立指定できるよう拡張(横長ロゴ等、正方形でない画像に対応)。
  • templates/index.html: マンガサブタブに「🎴 背景パターン」ボタンを追加。
  • static/js/i18n.js: パターン名・パラメータラベル(色・不透明度・サイズ・回転・幅・高さ・間隔等)をja/en/zh 3言語に追加。
  • static/js/main/01-state.js: initMangaBgPatternButton()の呼び出しを追加。
  • モーダルUI: 右パネルのパラメータ領域にモーダル本体固定高さ980×780px(初期900×640pxから拡大)とし、パラメータ増加後もスクロール操作なしで全項目が収まるよう調整(overflow-y:autoのスクロールラッパー自体はセーフティネットとして残置)。

デモ画像・README:

  • docs/11_pixifx.png〜docs/14_bgpattern.pngをKaptureで撮影。モーダルがposition:fixedのためDOM要素selector指定キャプチャでは要素が重複表示される不具合があり、フルページキャプチャ(scale=1)→モーダルの背景色(.tsm-dialogはrgb(45,45,45)、独自CSSのfilter-lib-modalはrgb(22,33,62)のヘッダー等)をnumpyでピクセル実測→Pillowで正確にクロップする方式に統一。
  • README.md/README_en.md/README_zh.mdのスクリーンショット節に4枚追加し、テキスト/Shapeツールの塗りテクスチャの位置X/Y指定・マンガツールの背景パターンを機能一覧の説明文に反映。

検証(Kapture): 全10パターンのサムネイル生成・プレビュー切替・パラメータ変更(色/不透明度/サイズ/回転/透過切替)を確認。市松模様を実際にオーバーレイへ適用しレイヤーに画像として挿入されることを確認後、Undoで元に戻した。回転90°で横縞パターンが縦縞に変わることを確認し、回転の実装が正しく機能することを確認。

How to apply: CanvasPatternはcreatePatternの時点で無限に繰り返されるタイル平面になるため、pattern.setTransform()による回転・平行移動は個々のタイル内部の描画ロジックを一切変更せずに安全に適用できる(境界の継ぎ目を考慮する必要があるのは、タイル内部の柄そのものを回転させて描画する場合のみ)。またKaptureでposition:fixedのモーダルをスクリーンショットする際、DOM要素selector指定は要素が重複して撮れることがあるため、フルページキャプチャ+既知の背景色でのピクセル実測クロップ(comic-creator-workflow既出の教訓)を優先する。


2026-07-24(テキスト/Shapeツールの塗りテクスチャに座標オフセットを追加、座標ずれを解消)

「レイアウトタブ、imageタブのテキストのスタイルで塗りでテクスチャ選択時の座標ずれの改善と座標設定を追加したい。※ドローツールの塗りテクスチャと同様にしたい」との依頼を受けて調査したところ、レイアウトタブのSVGテキストは<text x= y=>属性を直接書き換えて移動する仕組みのため、patternUnits="userSpaceOnUse"のテクスチャパターンが絶対座標に固定されたまま取り残され、ドロー図形のrect/ellipse/lineが元々抱えていたのと同じ問題(既に_drawShapeSyncTexturePatternTransformで対処済み)を抱えていることが判明。加えてテキストスタイルのUI自体にドロー図形にある座標オフセットX/Y入力欄が存在しなかった。続けて「Imageタブのシェイプの塗りテクスチャにもオフセット設定を追加したい」との追加依頼も同じ流れで対応した。

実装:

  • static/js/text-style-modal.js: テクスチャパネルにドロー塗りと同じ座標オフセットX/Y入力欄を追加。状態の保存・読込・スタイル適用のすべての経路に反映。
  • static/js/main/09e-text-tool.js: _textSyncTexturePatternTransform(テキスト移動時に移動分をパターンへpatternTransformとして反映する追従補正)・_textSyncTexturePatternScale(フォントサイズ変更=リサイズ時にタイルサイズ・オフセットを再計算)を新設し、既存のドラッグ/リサイズハンドラおよびスタイル適用関数_fontMgrApplyStyleAttrsToTextEl(パターン生成時点のx/yを追従の基準座標として記録)から呼び出す。
  • static/js/image-tab.js: Canvas版_textFillStyleのpattern.setTransformにオフセットのtranslateを追加。Imageタブは各テキストレイヤーが独立canvasのため座標ずれ自体は元々起きない構造だが、オフセットの入力自体が反映されていなかった分を追加。
  • static/js/image-tab/ShapeTool.js / static/js/image-tab.js: シェイプツール(Rect/Ellipse)の塗りテクスチャにoffsetX/offsetYを追加。Imageタブの図形は確定時にレイヤーへラスタライズされるため座標ずれ自体は起きない構造で、純粋にオフセット機能の追加のみ。
  • static/js/i18n.js: font.texOffsetX/font.texOffsetYをja/en/zhに追加。

検証(Kapture): テキストスタイルモーダル・ImageタブシェイプツールのオプションバーにオフセットX/Y入力欄が表示されパラメータ変更でエラーが出ないことを確認。レイアウトタブでテキストを選択→スタイルモーダルでテクスチャモードへ切替→UI表示を確認。テクスチャ画像のアップロード自体はOSネイティブのファイル選択ダイアログを介するためKaptureからは自動操作できず、移動時の追従挙動そのものの実演確認はユーザー側での確認に委ねた。

How to apply: 「transformで移動する要素」と「属性を直接書き換えて移動する要素」が混在する実装では、後者だけテクスチャパターンが絶対座標に取り残される。新しい要素タイプに塗りテクスチャ機能を追加する際は、その要素がどちらの移動方式かをまず確認し、属性直書き方式であればパターンの追従補正(生座標の記録+移動時のpatternTransform差分適用)を忘れずに実装すること。


2026-07-24(ImageタブのMaskツールにInpaint(Workflow Studio連携)を追加)

「workflow studioに追加したインペイント機能をcomic creatorにも追加したい。workflow studioのImage EditタブのインペイントメニューをImageタブに追加、Runでworkflow studioで処理して結果を表示したい。workflow studioがインストールされていない場合インペイントメニューは非表示にしたい」との依頼を受けて実装。ユーザーからのフィードバックで2点の設計変更(I2I設定と同様にInpaint専用のデフォルトワークフロー設定を追加/独立した「Inpaint」ツールボタンではなくMaskツールのサブツールバーに統合)を反映し、実機検証で見つかった3件の不具合も同じ流れで修正した。

Comic CreatorのImage タブはもともとComfyUI-Workflow-StudioのImage Editタブを移植したもの(image-tab.js冒頭コメントに明記)。Workflow Studio側にはその後Inpaintツールが追加されており、既存のI2I連携(iframe.contentWindow._wfmReceiveImageForI2Iを直接呼ぶ同一オリジンiframe方式)と同じパターンでInpaintの連携ブリッジを新設した。

実装(Comic Creator側):

  • static/js/image-tab.js: Maskツールのサブツールバー(Paint/Color/Alpha/Text/Vector/Shape/SAM3の右)に、Workflow Studio導入時のみ表示される「Inpaint」ボタンを追加(_switchMaskSubtool("inpaint"))。_renderMaskPropsにsub === "inpaint"分岐を新設(Positive/Negativeプロンプト・Grow Mask By・Denoise・Run・ステータス行)。_exportMaskCanvas(黒背景+白マスクのグレースケール書き出し)・_runInpaint()(合成画像+マスクを送信→結果を新規レイヤー「Inpaint Result」として追加)・_checkWfmAvailability()(/wfm疎通確認、既存の_checkBiRefNetAvailability等と同パターン)を新規追加。
  • static/js/main/14-integrations.js: 既存の_i2iSettingsと同構造で_inpaintSettings(localStorageキーccc_inpaint_settings)・initInpaintSettings()を新設(I2Iとは独立したInpaint専用デフォルトワークフロー設定)。sendInpaintToWorkflowStudio()を新設(loadWfmGalleryTab()でiframeロードを保証しつつ、switchTabは呼ばずImageタブの表示のまま裏側で実行)。
  • static/js/main/01-state.js: 設定タブ表示時にinitInpaintSettings()を呼ぶよう追加。
  • templates/index.html / static/js/i18n.js(ja/en/zh): 設定タブに「Inpaint設定」ブロックを追加。
  • static/js/image-tab/LayerManager.js: mergeLayers()が統合結果を常にtype: "image"で作成していたため、選択レイヤー全てがtype: "mask"の場合は統合結果もtype: "mask"として作成し、合成も各マスクのAdd/Subtractに応じたlighten/destination-out(既存の_buildMaskCanvasと同じ規約)に変更。従来は統合後に通常の画像レイヤーになってしまい、未ペイント部分(黒として扱われるべき箇所)が下のレイヤーを透過してしまっていた。
  • Inpaint実行時のマスクレイヤー選択(アクティブなマスク優先、なければ最前面のマスク)に.visibleチェックを追加し、非表示のマスクレイヤーは対象から除外されるよう修正。

実装(Workflow Studio側、comfyUI-wf-maneger/ComfyUI-Workflow-Studio):

  • static/js/image-edit-tab.js: 既存の_runInpaint()から共通処理を_runInpaintWithImages()として切り出し、外部(Comic Creator等)向けの公開エントリポイントrunInpaintExternal()を新設。マスクレイヤー選択に同じく.visibleチェックを追加。
  • static/js/gallery-tab.js: 既存のwindow._wfmReceiveImageForI2Iと同じ場所にwindow._wfmReceiveInpaintRequestを新設(デフォルトワークフローの任意プリロード→runInpaintExternal呼び出し→結果URLまたはエラーを返す)。
  • static/js/comfyui-editor.js: _loadImageElement()内src instanceof Blobが、別ウィンドウ(Comic Creator)で生成したBlob/Fileを別レルム(iframe)で判定するとコンストラクタの参照が異なり常にfalseになるバグを発見・修正(typeof src !== "string"による実体判定に変更)。修正前はimg.srcにBlobオブジェクトがそのまま代入され文字列化された[object Blob]がURLとして扱われ404エラーになっていた。

検証(Kapture、実機http://127.0.0.1:8189/ccc): マスクレイヤーを描いてInpaint実行→Workflow Studio側で生成→結果が「Inpaint Result」レイヤーとして追加されるまでの一連の流れを確認。Inpaint用デフォルトワークフローのON/OFF両方の経路を確認。非表示マスクレイヤーでRunした場合にガードメッセージが出ることと、表示状態に戻すとガードを通過することを確認。マスクレイヤー2枚を別々の位置にペイント→Shift選択→統合→結果レイヤーがマスク用アイコン(黒背景サムネイル)で表示され、ツールパネルもMask用のオプションのままであることを確認。

How to apply: 同一オリジンiframe越しに別ウィンドウで生成したBlob/Fileを直接引数として渡す連携パターンでは、受け取り側でinstanceof Blobのようなコンストラクタ同一性に依存するチェックを使わないこと(typeof x !== "string"等、実体で判定する)。ウィンドウをまたぐと同じ仕様のオブジェクトでもinstanceofは必ずfalseになる(Blob自体はcreateObjectURL等では別レルムでも問題なく使えるため、チェック方法だけの問題)。またマスクレイヤーは「透過背景+白ペイント、未ペイント部分は黒として扱われる」という規約に依存する処理(エクスポート・サムネイル・統合等)が複数箇所にあるため、マスクレイヤーを加工する新機能を追加する際は統合先のtypeが"mask"のまま維持されるかを必ず確認する。


2026-07-23(レイアウトのドローに「ベクター曲線」を追加、テクスチャ塗りの追従・位置指定・PNG変換時の欠落を修正)

「レイアウトタブのドローツールにベクター曲線を追加したい。ImageタブのMaskツールのVectorのように描きたい」との依頼を受けて実装。続けてユーザー自身の検証で3件の追加不具合が見つかり、同じ流れで修正した。

1つ目は新機能で、既存の「多角形」ペンツール(クリックで頂点追加・直線で結ぶ)と対をなす、Catmull-Romスプラインでなめらかに結ぶベクター曲線ツール。ImageタブのMaskVectorTool(image-tab/MaskEditorOneTools.js)と同じ操作感・同じ補間式に合わせた。

2〜4件目はユーザーが新機能を検証中に見つけた既存不具合。「ベクター曲線・曲線にテクスチャ塗りを設定してシェイプを動かしても座標が変わらないが、矩形・楕円・多角形では変わる」→「矩形・楕円・多角形をベクター曲線と同じ(動かしてもズレない)挙動に揃えたい、その上で座標も設定可能にしたい」→「テクスチャを使ったシェイプを『図形をPNG変換』すると塗りがない状態になる」→「フキダシを画像に変換すると左・上が切れる」の順で報告され、いずれもSVGのfill="url(#...)"(グラデーション・テクスチャパターン)やgetBBox()の挙動に起因する根の深い箇所だったため、原因調査から着手した。

実装:

  • templates/index.html / static/js/i18n.js(ja/en/zh): シェイプ選択に「ベクター曲線」ボタン・選択肢を追加。
  • static/js/main/17a-layer-draw-input.js: _layerDrawVecClick/_layerDrawVecPreview/_vecBuildSplineCtx/_vecBuildSplinePathD/_layerDrawVecCommit等を新設。クリックでノード追加、3点以上で始点付近クリック→閉じたシェイプ(<path>、塗り適用)として確定、Enterキー→開いた線(fill="none"固定)として確定、Escキーで直前のノードを取消。多角形の確定処理にも、テクスチャ追従用の生座標(data-raw-x/y/w/h)記録を追加。
  • static/js/main/17c-layer-draw-handles.js: 新設_drawShapeSyncTexturePatternTransform(el)。矩形・楕円・線・多角形はSVG属性(x/y/points等)を直接書き換えて移動するため、patternUnits="userSpaceOnUse"のテクスチャパターンが絶対座標に固定されたままシェイプに対して滑って見える問題があった(曲線・ベクター曲線はtransformで移動するためパターンも一緒に動き問題なし)。作成時点の生座標→現在のbboxへのアフィン変換をpatternTransformとしてパターンにも適用し、_drawShapeSetBounds(移動・リサイズ時)と多角形の頂点個別ドラッグの両方から呼ぶことで、path/g系と同じ「テクスチャがシェイプに対して動かない」挙動に統一。回転は元々シェイプ自身のtransform="rotate(...)"で行われておりパターンにもそのまま継承されるため対象外。
  • static/js/main/17b-layer-draw-commit.js: 矩形・楕円・線の作成時にも生座標(data-raw-x/y/w/h)を記録。
  • static/js/main/09e-text-tool.js: 共通の塗り適用関数_fontMgrApplyFillPaintToElにテクスチャの位置オフセット(offsetX/offsetY)を追加し、パターンのx/y属性および往復用のdata-ccc-tex-offset-x/yに反映。抽出側_fontMgrExtractStyleFromTextEl・17cの_drawShapeExtractFillStateも同項目を読み戻すよう対応。
  • templates/index.html / 17a-layer-draw-input.js: テクスチャ塗りパネルに位置X/Y入力を追加(UIへの反映・保存・選択図形からの復元)。
  • static/js/main/09c-balloon-handles.js(convertShapeToImage。フキダシ「画像に変換」とドロー「図形をPNG変換」の共通処理): (a) クローンしたシェイプだけの単独SVGに元の<defs>(グラデーション・テクスチャパターンの定義本体)が複製されておらず、fill="url(#id)"の参照先が解決できず塗りが消えていたため、<defs>もクローンして持たせるよう修正。(b) 余白計算がel自身のstroke-width属性しか見ておらず、フキダシの枠線は.h2-layer-borderなど子要素側にあるため取得できず、実際の線幅を大幅に下回る固定4px余白にフォールバックしていた(getBBox()は塗りの幾何形状のみでstrokeのはみ出し分を含まないため、この余白不足がそのまま輪郭のクリッピングになっていた)。自身+子孫の中の最大stroke-widthを探すよう修正。

検証(Kapture): ベクター曲線は閉じたシェイプ(Catmull-RomによるCコマンドの<path>、Zで閉じ塗り適用)・Enterでの開いた線(fill="none"、Zなし)の両方をDOM上で確認。以降の3件はユーザー自身の実機検証により解消を確認。

How to apply: SVGのpatternUnits="userSpaceOnUse"(x/y省略)は参照元の要素がtransformで動く場合は自動的に追従するが、属性を直接書き換えて移動するタイプの図形では絶対座標に取り残される。「transformで動く図形」と「属性直書きで動く図形」が混在する実装では、後者にも生座標との差分をpatternTransformとして明示的に載せないと挙動が揃わない。また要素を単独SVGとしてクローン→ラスタライズする処理(convertShapeToImage等)では、url(#...)参照の定義本体(<defs>)を必ず一緒に複製すること、およびstroke-widthは要素自身ではなく子孫要素にあるケースを想定して余白を計算すること。


2026-07-23(3Dポーズタブに視線ターゲット・揺れ物理トグルを追加)

「現在使用しているライブラリで追加可能な機能」の検討依頼を受け、3Dポーズ機能(実体は別カスタムノードcomfyui-vrm-pose-editor)が使うthree-vrmライブラリを調査。VRMLoaderPluginがspringBonePlugin・lookAtPluginを内包しており、vrm.lookAt(VRMLookAt)・vrm.springBoneManager(VRMSpringBoneManager、揺れボーンがあるモデルで生成)が既にロード時点で存在し、VRM.update(delta)が毎フレーム両方を自動更新していることが判明。つまり視線追従・揺れ物理は「ライブラリ内に実装済みだが配線されていないだけ」の状態だったため、UIから使えるように配線した。

合わせてHDRI環境ライティング(擬似.hdr、THREE.PMREMGenerator+手続き生成グラデーション)も実装したが、Kapture実機検証でキャンバス全体1ピクセルも変化しないことを発見。原因切り分けの結果、ブラウザのOES_texture_half_float_linear(half-floatテクスチャの線形フィルタリング)拡張が未対応で、PMREMGeneratorがエラーを出さずに空の環境マップを生成していたと判明。加えてVRMのMToonシェーダーは元々envMap/scene.environmentを参照しないためキャラ本体には無関係(Ground/BG Wallの反射のみに影響)という効果の限定性もあり、ユーザー判断で機能ごと削除した。

実装(comfyui-vrm-pose-editor側。SPAの3Dポーズタブは動的importでこのコードをそのまま利用):

  • js/pose_editor_core.js: ドラッグ可能なシアン色マーカー(lookAtHelperMesh)を追加し、ON時にvrm.lookAt.targetへ割り当てて目・頭を追従させる(hasLookAt()/getLookAtEnabled()/toggleLookAt())。マーカーはcapture()時に自動非表示化、新モデル読込時はVRM0/VRM1の正面向きに応じた位置へ再配置。揺れ物理は、ポーズの瞬間切替(リセット/ポーズ読込/ミラー)直後にspringBoneManager.setInitState()で新ポーズへ再アンカーして「一瞬跳ねる」問題を解消し、ON/OFFトグルはcapture()で既に使われている「delta=0で一時停止」と同じ手法をアニメーションループに適用(hasSpringBones()/getSpringBoneEnabled()/toggleSpringBoneEnabled())。
  • js/pose_editor_3d.js(ComfyUIノードUI)・comfyui-comic-creator/templates/index.html+static/js/main/23-pose3d-bridge.js(SPA UI)の両方に「👁 視線」「🎐 揺れ」トグルボタンを追加。
  • static/js/i18n.js: layout.pose3dLookAtTitle/layout.pose3dSpringBoneTitleをja/en/zh 3言語に追加。
  • (削除済み)HDRI環境ライティング: pose_editor_core.jsのEnvironment lightingセクション・API、light_editor.jsのEnvセレクトUI・ライトライブラリプリセットへの永続化を全て実装後に削除。

検証(Kapture): ユーザーが実際にVRMモデルを配置した状態で、視線ターゲットのマーカー表示/非表示・トグル動作、揺れ物理トグル(hasSpringBones: trueのモデルで確認)のクリック時無エラーを確認。HDRIについてはeditor.setEnvironmentPreset()をキャンバス全ピクセルでdiff比較するテストで実装バグを検出し、上記の通り原因究明後に機能ごと削除する判断に至った。

How to apply: サードパーティ製3Dライブラリ(three-vrm等)に機能追加を頼まれたら、まず「vendorされているクラスに実はもう実装されているが未配線なだけ」のケースがないか(該当クラス名でgrep)を先に調べる。THREE.PMREMGeneratorのような高度なGPU機能に依存する実装は、gl.getSupportedExtensions()で対象拡張(OES_texture_half_float_linear等)の対応有無をKaptureのevaluateで実機確認してから着手しないと、エラーが出ないまま無効化したように見える「サイレント失敗」に気づけない。詳細はvrm-pose-editor-architectureメモリに記録。


2026-07-22(PDF/EPUB/zip出力のユーザー操作エラーを修正、画像出力の解像度メタデータを追加)

下書きレイヤー機能の確認後、「出力を試したところPDF/EPUB/zip保存でエラーになる」との報告(Failed to execute 'showSaveFilePicker' on 'Window': Must be handling a user gesture to show a file picker.)を受けて調査・修正した。原因は、showSaveFilePicker()はクリック等のユーザー操作から間を置かず呼ばないと失敗する仕様のところ、PDF/EPUB/zip出力は保存ダイアログを開く前に「全ページの描画・フォント埋め込み・PDF/ZIP生成」という重い非同期処理を挟んでいたため、処理に時間がかかる(ページ数が多い等)とユーザー操作の有効期限が切れていたこと。PNG単ページ出力は処理が短く済むため、たまたま失敗しにくかった。

続けて、出力したPNG/JPEGのファイルプロパティでdpiがWindows既定値の96、WebPだけ72と表示され食い違うとの指摘を受け調査したところ、このアプリはPNG/JPEG/WebPのいずれにも解像度メタデータを一切埋め込んでおらず、Windowsのプロパティ画面がコーデックごとに異なる既定値を表示していただけと判明。実際に選択したdpiをファイルへ埋め込む実装を追加した(最終的にはWindowsのプロパティ画面自体がこの情報を反映しない仕様と分かったが、Photoshopでは正しく72dpiと確認できたためファイル側の実装は正しいと判断)。

実装:

  • static/js/main/13-export-pdf-epub.js: showSaveFilePicker()をクリック直後(重い処理の前)に呼んでハンドルだけ確保する_pickSaveTarget(fileName, mimeType, ext, description)を新設。_saveBlob()は確保済みハンドルを受け取れるよう拡張。exportToPdf/exportToEpubは関数の冒頭で先にハンドルを確保し、ページ生成後にそのハンドルへ書き込む順序に変更。
  • static/js/main/12-text-png-export.js: handleExport()のzip保存も同じパターンで、ページ描画ループの開始前に_pickSaveTargetでzipファイルのハンドルを確保するよう修正。
  • static/js/main/13a-export-metadata.js: 画像出力メタデータ埋め込みに解像度(dpi)埋め込みを追加。PNGはpHYsチャンク(メートルあたり画素数に変換)、JPEGは既存JFIF APP0セグメント内のdensityフィールドを上書き(セグメント長は不変のため新規挿入不要)、WebPはXResolution/YResolution/ResolutionUnitの3タグのみを持つ最小TIFF blobをEXIFチャンクとして新設しVP8Xヘッダにフラグを設定。_embedImageMetadata()は、タイトル等のテキストメタが空でも解像度は常に埋め込むよう変更(「手動」選択時はPDF出力と同じ既定値96dpiを使用)。

検証: PDF/EPUB出力のエラーはユーザー確認により解消を確認。画像出力(PNG/JPEG/WebP)でも一度エラー報告があったが、コンソールエラー・JSダイアログとも発生しておらず、ネイティブ保存ダイアログが裏に隠れていただけとユーザーの再確認で判明(コードの問題ではなかった)。解像度メタデータのバイナリ構造は、Node.js上でフェイクのPNG/JPEG/WebPバイト列を生成し、埋め込み関数を実行→生成されたpHYs/JFIF density/EXIFタグの値を逆算してdpiが正しく往復すること、テキストメタ情報との併用時もチャンク順序が壊れないことをテストスクリプトで検証済み(ブラウザでの実クリックはネイティブファイル選択ダイアログが絡むため自動操作不可)。Windowsのプロパティ画面には反映されない(Windows側の仕様)が、Photoshopでは正しい値が確認できている。

How to apply: showSaveFilePicker()/showDirectoryPicker()など「ユーザー操作の有効期限(transient activation)」が必要なAPIは、ボタンのクリックハンドラ内であっても、呼び出す前に重い非同期処理(複数ページのレンダリング等)を挟むと失敗しうる。対策は「先にピッカーを呼んでハンドルだけ確保→重い処理→確保済みハンドルへ書き込み」の順序に組み替えること(showDirectoryPickerは元々この順序だったため、showSaveFilePicker側もそれに合わせた)。またPNG/JPEG/WebP等の画像フォーマットの「解像度(dpi)」表示は、ファイル自体にメタデータが無ければビューア・OS側の既定値が使われ、フォーマットごとに既定値が異なりうる(Windows Explorerのプロパティ画面はさらにOS側で反映されない場合がある)。dpiを意図通り一致させたい場合は、ピクセル寸法の計算とは別に、各フォーマット固有のメタデータフィールド(pHYs/JFIF density/EXIF等)への明示的な書き込みが必要。


2026-07-22(下書きレイヤー機能を追加 — レイアウト+Imageタブ連携)

「レイアウトのレイヤーに下書きレイヤーを追加したい。オーバーレイのように全コマの上に表示されるが、クリックしても無視されその下のレイヤーが操作可能・出力にも含まれない」との依頼を受けて実装した。編集方法は「編集モード切替方式」(選択中のみ操作可能)、対応コンテンツは「画像のみ」で合意したうえで着手。続けて「下書きのラフスケッチをImageタブで作成できるようにしたい。Newボタン右隣に下書きボタンを追加、作品サイズ(72dpi換算)でキャンバス作成」という関連依頼も同じ流れで実装した。

実装:

  • static/js/main/01-state.js: state.selectedDraftを追加。ESモジュールのimage-tab.jsからclassic script側のstate.activeWorkを安全に参照するためwindow._ccGetActiveWorkブリッジを追加。
  • static/js/main/08-panels-images.js: オーバーレイ実装(insertImageToOverlay/getOrCreateOverlayGroup等)を踏襲し、insertImageToDraft/getOrCreateDraftGroup/selectDraft/saveDraftSvgを新設。_syncDraftInteractivity(svgEl)で編集モードに応じて下書き内画像のpointer-eventsをauto/noneに切替(非選択時は常にクリックが透過)。initPanelsOnSvgで下書きg要素をオーバーレイのさらに前面に配置。
  • static/js/main/07-pages.js: buildMergedSvg(pageRecord, opts)にopts.includeDraftを追加し、プレビュー描画(renderLayoutTab)でのみtrueを渡す。PDF/EPUB/PNG連番等の出力側は変更なしのため下書きは自動的に出力対象外になる。savePanelSvgに__draft__のディスパッチを追加。
  • static/js/main/03-layers-panel.js / 05-groups-move.js / 06a-polygon-geometry.js: 保存先パネルID解決ロジック(syncPanelSelectionToObject・複製/移動・レイヤー並べ替え)に下書き対応を追加。下書き内画像はdata-panel-id="__draft__"を持つため、既存の「画像はdata-panel-id属性を最優先で使う」という規約(オーバーレイ実装で既に使われていたパターン)にそのまま乗せられた。
  • static/js/main/04b-layer-panel-render.js: レイヤーパネル最下段にオーバーレイと対になる「📝 下書き(全面)」行を追加(画像のみ表示、マスクボタンは非表示)。
  • static/js/main/03-layers-panel.js: 複製/移動先ドロップダウンに「下書きへ」を追加。下書きは画像専用のため、画像以外を複製/移動しようとした場合はエラーメッセージ(layer.draftImagesOnly)を表示。
  • Imageタブ: templates/index.htmlに「下書き」ボタン(New/Close間)を追加。static/js/image-tab.jsの_newCanvas()から共通ロジックを_createNewCanvasWithSize(w, h, baseName)として切り出し、_newDraftFromActiveWork()を新設(work.width/height=1/100mm単位を/100/25.4*72でpxへ変換、サイズ入力ダイアログなし)。「レイアウトに送る」(_saveToLayout)は、送信元がbaseName==="draft"のキャンバスの場合、現在の選択に関わらず自動的に下書きレイヤーへselectPanel('__draft__')で切り替え、作品サイズそのままでページ全面挿入する(既定の「40%センター配置」だとサイズ対応が崩れるため)。
  • static/js/i18n.js: common.draftFull/image.draftBtn/image.draftBtnTitle/layer.draftOptionTo/layer.draftImagesOnly等をja/en/zh 3言語に追加。

検証(Kapture): レイヤーパネルの「下書き」行選択・パネル選択ドロップダウン経由での編集モード切替、他コマ/オーバーレイ選択時にクリックが正しく透過する(pointer-events)こと、コマ内画像の「下書きへ複製」で下書きレイヤーに正しく配置され即座にドラッグ可能になることを実機で確認。Imageタブの「下書き」ボタンでは、作品サイズ(29700×21000 = A4横)から72dpi換算した842×595pxのキャンバスがダイアログなしで正しく作成されることを確認。ファイル選択ダイアログが絡む画像アップロード・保存操作は自動操作できないため未検証。

How to apply: オーバーレイのような「ページ全面レイヤー」を新設する際は、既存実装(データモデル・DOM属性・保存関数・pointer-events制御・レイヤーパネル行)を1対1でミラーリングするのが最も低リスク。差分(下書きは編集モード時のみ操作可・出力除外・画像のみ対応)は、ミラーリングした実装に対するピンポイントな上書き(_syncDraftInteractivityのpointer-events切替、buildMergedSvgのopts分岐、対応オブジェクト種別のガード)として追加すると影響範囲を最小化できる。既存コードが「data-panel-id属性を保存先解決の最優先に使う」という規約を既に持っていた(オーバーレイ実装で先例あり)ため、新レイヤーもそれに乗せるだけで済み、保存先解決ロジックを新規設計せずに済んだ。


2026-07-22(フキダシ内包テキストの統合・縦書き対応・尻尾幅デフォルト変更)

前日(07-21)に追加した「フキダシ+テキスト作成」モーダルは、四角/角丸四角/楕円の3形状限定の新規シンプル形状(textbox-*)専用で、既存の尻尾付きフキダシ全形状(通常/角丸矩形/思考/バクダン/雲もこもこ/雲なみなみ、以下h2タイプ)とは別の独立した仕組みだった。「フキダシ+テキスト作成と既存のフキダシを統合したい。テキストは縦書きにも対応させたい」との依頼を受け、既存フキダシ全形状にテキスト内包機能を統合した。ユーザー方針: 「フキダシ形状の作成・微調整(既存のh2挿入ボタン+ハンドル操作)」と「テキストの詳細設定(モーダル)」の導線は2つのまま維持する。

実装:

  • static/js/main/09f-bubble-text.js を全面改修。_bubbleTextAreaFor(el)(形状ごとにテキスト内包エリアの cx/cy/rx/ry/kind を算出。バクダン・雲もこもこ・雲なみなみは輪郭が凹凸なため rx/ry を0.75倍に縮小)・_bubbleTextRenderText(el, area)(横書き/縦書き共通のテキスト描画)・_bubbleTextSyncH2Text(el)(h2タイプ用の同期エントリ)を新設。static/js/main/09b-balloon-shapes.js の _updateH2ShapePath 末尾に _bubbleTextSyncH2Text 呼び出しを追加し、本体path生成後にテキストがあれば内包描画するようにした。テキストの折返し幅・高さは常に「現在のフキダシのrx/ry」を基準にし、テキスト量に応じて箱を自動拡大することはしない(箱のサイズ調整はハンドル操作に委ねる設計。textbox-*も同じ挙動に統一)。
  • 「フキダシ+テキスト作成」ボタンを「テキストを内包」に役割変更。選択中のフキダシ(textbox-*またはh2タイプ)へのテキスト設定専用とし、モーダルから形状選択(四角/角丸/楕円)ボタンを削除。textbox-*の新規作成導線は廃止し、既存データの編集のみ後方互換で維持(新規にシンプル形状を作りたい場合は、h2ツールで角丸矩形/通常を挿入してからテキストを内包する2ステップに統一)。
  • 縦書き対応の実装中に、writing-mode="tb" というSVG1.1属性値が、CSS Writing Modesを実装する現行ブラウザ(有効な値は horizontal-tb/vertical-rl/vertical-lr のみ)では無効な値として無視され、実際には機能していないバグを発見。09f-bubble-text.js と、同じ実装だった単独テキストツール 09e-text-tool.js(_setTextElVertical)の両方を、属性ではなく style.writingMode = 'vertical-rl' + style.textOrientation = 'upright' に修正した(既存の単独テキストの縦書きも同時に直った)。
  • 追加依頼で、モーダルに文字色セレクト(黒/白/赤/青、dataset.textColor に保存)と、フォント選択のGoogle/システム/カテゴリタブ切替(レイアウトタブのテキストツールと同じ構成、_bubbleTextInitFontTabs を新設)を追加。モーダル幅を480px→560pxに拡大。
  • 縦書きの上/下寄せが実際の見た目と逆になる不具合を発見・修正。原因は各tspanのy座標が常にエリア中心(cy)固定のままtext-anchor(start/end)だけで上下を切り替えていたため、テキストが中心から片側に伸びるだけで見た目の上下が意図と逆転していたこと。横書きのX方向計算と対称に、上寄せ/下寄せ選択時はエリアの上端/下端(cy - ry + padding / cy + ry - padding)を基準点にするよう修正。あわせて、モーダルの文字色セレクトを独立行から「文字サイズ・縦書き」の行(縦書きチェックボックスの右隣)へ移動。
  • 「フキダシの尻尾パラメータ『幅』のデフォルトを30°から13°に変更したい(楕円/角丸矩形/バクダン/雲もこもこ/雲なみなみ)」との追加依頼を受け、templates/index.html のスライダー初期値、09c-balloon-handles.js の新規挿入時初期値・UI同期のフォールバック値、09b-balloon-shapes.js のパス生成フォールバック値など、tailWidth のデフォルト値30が使われている全箇所を13に変更。

検証(Kapture): 楕円・角丸矩形・バクダン・雲もこもこ・雲なみなみへのテキスト内包(横書き・縦書き)、縦書きの上/中央/下寄せが実際にフキダシの上部/中央/下部に配置されること、文字色「赤」+システムフォント切替の反映、新規挿入したフキダシの dataset.tail-width が13になることをDOM・スクリーンショット双方で確認。テスト用に追加した要素は削除し元のページ内容を維持したまま完了。

How to apply: SVGの縦書きは writing-mode をSVG1.1のレガシー値(tb等)で属性指定するのではなく、CSS Writing Modes準拠の値(vertical-rl/vertical-lr)を style プロパティとして設定する(現行ブラウザはCSS Writing Modes実装のため属性値は無視されうる)。text-anchor だけでテキストブロックの整列(上/中央/下や左/中央/右)を制御する実装は、基準点となる x/y 座標を「align に応じてエリアの端へ動かす」処理とセットでないと、中心固定のままでは見た目が意図と逆に見えることがある — 横書き・縦書きどちらでも対称に扱うこと。


2026-07-21(@imgly/background-removal のローカル同梱を見送り、モデル品質セレクトを追加)

PLAN_backlog「@imgly/background-removal のローカル同梱可否の判断」に着手。当初「同梱で」との依頼だったが、実際に必要なファイルを調査したところ想定以上に大きく、方針を変更した。

調査結果: npm本体(JSコード)は解凍後約2MBで問題ないが、実行時にCDN(staticimgly.com)から取得しているWASMランタイム+ONNXモデルは合計で最大約326MB(CPU用WASM 10.7MB + WebGPU用WASM 20.7MB+small/medium/largeの3モデル計294MB)。現在CDN版が使っているデフォルト設定(device=cpu, model=medium=isnet_fp16)を維持するだけでも約95MB。本リポジトリはGitHub公開リポジトリ(.gitが現状約9MB)で、GitHubの単一ファイル上限は100MB(largeモデル168MBは超過しGit LFSが別途必要)。一度gitに取り込むと履歴からの除去も手間がかかるため、ユーザーに実測値を提示のうえ再確認し、同梱は見送りCDN依存を維持、代わりに背景除去のモデル品質をアプリ上で選べるようにする方針に決定した。

実装: removeBackground() の model オプション("small"=isnet_quint8・42MB / "medium"=isnet_fp16・84MB・既定 / "large"=isnet・168MB)をUIから選べるようにした。

  • レイアウトタブ(templates/index.html + static/js/main/16-processing-edit-tabs.js): 「画像」サブタブの背景除去モデル選択(軽量/BiRefNet)の右に品質セレクトを追加し、_procRemoveBackground/_procRemoveBackgroundImgly に quality 引数を通した。モデル選択が「軽量」以外(BiRefNet)のときは非表示。i18n 3言語対応(layout.bgQuality* キー追加)。
  • Imageタブ(static/js/image-tab.js): BG Removeツールのオプションバーにも同じ品質セレクトを追加し、_bgRemoveImgly/_applyBgRemove に quality 引数を通した。既存のBG Removeパネルの慣例(英語のみ、i18n未対応)に合わせて英語表記のまま実装。モデル選択がBiRefNetのときは非表示(change イベントで切替)。
  • どちらもデフォルトは既定値の "medium" のため、既存ユーザーの挙動・見た目は変わらない(後方互換)。

検証(Kapture): 両タブでQuality/品質セレクトが正しいラベル・デフォルト値(標準品質/Standard 84MB)で描画されることを確認。Imageタブでモデル選択をBiRefNetへ切り替えると品質セレクトが正しく非表示になることを確認。実際のモデルダウンロード・背景除去処理自体はCDN依存のまま変更していないため未実施(フェーズ1以前から動作実績あり)。

How to apply: 「CDNライブラリのローカル同梱」のような判断は、依頼時点の粗い見積り(「数十MB」)だけで進めず、実際にファイルを取得してサイズを確認してから最終判断を仰ぐのが安全。特に公開gitリポジトリでは、大きいバイナリの取り込みは後戻りしにくい(履歴除去が面倒、GitHubのファイルサイズ上限に抵触しうる)ため、確認なしに進めない。


2026-07-21(ドロー/シェイプの塗りにグラデーション・テクスチャ・塗りなしを追加 — 塗り拡張フェーズ2)

フェーズ1(テキスト塗り拡張)・フォントタブ対応に続き、レイアウトタブのドロー図形(矩形・楕円・直線・曲線・多角形・鎖・ロープ・My曲線)とImageタブのシェイプツール(矩形・楕円)にも同じ塗り設定(グラデーション・テクスチャ・塗りなし)を追加した(PLAN_backlog「塗りのグラデーション・テクスチャ・塗りなし対応」フェーズ2)。

設計判断(スケール基準): テキストの塗りはフォントサイズ100pxあたりの相対値だったが、ドロー図形にはフォントサイズに相当する基準がない。図形サイズ比例か固定かの選択肢のうち、k=1固定(テクスチャのスケール%がそのままSVG単位/canvas pxのタイルサイズになる、図形サイズに応じた追加スケーリングはしない)を採用した。理由: ドロー/シェイプの塗りは(フォントタブのスタイルのような)名前付き保存・再利用の対象ではなくオブジェクトごとの都度設定であり、単純な絶対値の方が予測しやすいため。

実装:

  • static/js/main/17a-layer-draw-input.js: _layerDrawFillState(mode/gradient/selectedStopIdx/texture)と、_layerDrawFillRampColorAt/_layerDrawDrawGradRamp/_layerDrawFillSyncUI/_layerDrawGetFillStyleObj/_layerDrawLoadFillStateFromShape を新設(text-style-modal.js の同名ロジックのミニマム移植)。initLayerDraw() にモード切替・ランプのドラッグ/追加/削除・テクスチャファイル選択(最大512px縮小)のイベントを追加し、_layerDrawApplyPropsToSelected() の塗り適用を _fontMgrApplyFillPaintToEl(el, svgEl, styleObj, 1)(09e-text-tool.js、フェーズ1で作った関数をそのまま流用。第1引数は任意の要素でよい設計だったため追加実装不要)に差し替えた。
  • static/js/main/17b-layer-draw-commit.js(矩形/楕円/直線/曲線等の確定)・static/js/main/17a-layer-draw-input.js(多角形ペンの確定): 図形生成時の el.setAttribute('fill', fillColor) を同じく _fontMgrApplyFillPaintToEl 呼び出しに置き換え。
  • static/js/main/17c-layer-draw-handles.js: _drawShapeExtractFillState(el, svgEl) を新設(_fontMgrExtractStyleFromTextEl の塗り抽出部分と同じロジックのミニマム版。テキスト専用のfont-weight等を読まない)。_drawShapeSyncProps() から呼び出し、選択中図形の塗りモード・グラデーション・テクスチャをUIへ復元する。
  • templates/index.html: レイアウトタブの描画ツールバーに塗りモードセレクト・グラデーションパネル(線形/円形・角度・カラーランプ・ストップ追加/削除)・テクスチャパネル(画像選択・スケール%)を追加。ラベルは font.* の既存i18nキー(fillModeSolid/fillModeGradient/fillModeTexture/gradShape*/texSelectImage/texScale)を再利用し新規キー追加なし。
  • static/js/image-tab/ShapeTool.js: fillMode/fillGradient/fillTexture({img, scale}。img は選択時に既にロード済みのImage要素で、Imageタブのシェイプは確定時に一度だけラスタへ焼き込むため既存の _getTextureImage のような非同期キャッシュは不要 — My曲線の originalImg と同じパターン)フィールドと addFillStop/removeFillStop(既存 FillTool.evalGradient を再利用)を追加。drawShape() の rect/ellipse 塗り部分を新設の静的メソッド _fillStyleFor(ctx, sh, x1, y1, w, h)(image-tab.js の _textFillStyle と同型。基準サイズは図形自身のバウンディングボックス)に置き換え。
  • static/js/image-tab.js: シェイプツールのオプションパネル(_renderToolOptions("shape"))に塗りモードセレクト・グラデーションパネル(_drawShapeGradRamp/_setupShapeGradRampを新設、FillツールのランプUIと同型)・テクスチャパネルを追加。矩形・楕円選択時のみ表示(線・鎖・ロープ・My Curveは塗り自体を持たないため対象外)。

検証(Kapture): レイアウトタブで多角形をグラデーション塗り(白→グレー、線形0°)で実際に描画し、linearGradient(objectBoundingBox、x1/y1/x2/y2が角度と一致)とfill="url(#...)"がDOMに正しく生成されることを確認。単色・塗りなしへの切り替えで旧defsが正しくクリーンアップされることも確認。選択中図形からの塗りモード再抽出(グラデーション→UIに復元)も動作確認済み。テスト図形はUndoで削除済み。ImageタブはUI(モード切替によるグラデーション/テクスチャパネルの表示切替)の動作を確認したが、矩形/楕円の実際のドラッグ描画はKaptureのクリック操作がmousedown+mouseupを同一座標で発行するため検証できず、コードレビュー(SVG側で検証済みの同型ロジックの流用、および既存の _textFillStyle/FillTool.evalGradient との構造的一致)に留めた。

How to apply: 同じ機能を複数の場所(テキスト/ドロー/シェイプ、SVG/Canvas)に展開する際は、汎用化した適用関数(_fontMgrApplyFillPaintToEl のように第1引数を「任意の要素」にしておく)は素直に使い回し、UI(モード選択・ランプ描画・ドラッグ操作)は対象ごとに小さく複製する方が、状態管理のスコープや呼び出しタイミングの違いを吸収する共通レイヤーを作るより変更コストが低い(フォントタブ対応時の教訓と同じ)。スケール基準がない対象(今回のドロー図形)には、既存の相対値方式を無理に当てはめず絶対値(k=1)を選ぶ判断も、複雑さを増やさないという同じ方針に沿う。


2026-07-20(フォントタブのスタイル編集にも塗りのグラデーション・テクスチャ・塗りなしを追加)

フェーズ1(テキスト塗り拡張)では、フォントタブのスタイル編集フォームは単色のみ対応のままで、拡張塗り(グラデーション/テクスチャ/塗りなし)はレイアウト/Imageタブの「スタイル」ボタンから開くモーダルでのみ編集可能にし、フォントタブ側は保存時に値を持ち回るだけ(_fontMgrEditingFillExt)としていた。追加依頼を受け、フォントタブのフォームにも同じ編集UIを実装した。

実装:

  • templates/index.html: #style-fill-color 周りに text-style-modal.js と同じ塗りUI(塗りチェックボックス+モードセレクト+グラデーションパネル(線形/円形・角度・カラーランプ・ストップ追加/削除)+テクスチャパネル(画像選択・スケール%))を追加。id は style- プレフィックス(モーダル側は tsm- プレフィックス)で名前空間を分離。
  • static/js/main/19-font-manager.js: _fontMgrEditingFillExt(保存値の素通し)を削除し、モジュールスコープの _fontMgrFillState(enabled/mode/gradient/selectedStopIdx/texture)と、_fontMgrHex2Rgb/_fontMgrRgb2Hex/_fontMgrRampColorAt/_fontMgrDrawGradRamp/_fontMgrSyncFillUI/_fontMgrLoadFillState を新設(text-style-modal.js の同名ロジックの移植・同一設計)。_fontMgrGetStyleFromUI/_fontMgrApplyStyleToUI/_fontMgrResetStyleUI/_fontMgrUpdateStylePreview を実状態ベースに書き換え、_fontMgrInitStyleTab に塗り関連イベント(モード切替・ランプのドラッグ/追加/削除・テクスチャファイル選択(最大512px縮小)・スケール入力)を追加。
  • レイアウト/Imageタブ・フォントタブのどちらで編集・保存しても同じ fontmgr_text_styles(v2形式)を読み書きするため、一覧・見た目は双方向で一致する。

検証(Kapture): フォントタブでモード切替(単色→グラデーション→テクスチャ→塗りなし)ごとにパネル表示・SVGプレビューが追従することを確認。ランプのストップ色変更(白→赤)がプレビューに反映され、名前を付けて保存→「新規」でリセット→保存済み一覧から再選択で、モード・ランプ・プレビューが完全に復元されることを確認。テスト用スタイルは削除済み。コンソールエラーなし。

How to apply: モーダルとタブ埋め込みフォームのように同じ編集UIを2箇所に持つ場合、id プレフィックスを分離(tsm- / style-)して同一ページ内での衝突を避けつつ、ロジック(状態管理・ランプ描画・ドラッグ処理)は関数名を変えて丸ごと複製するのが早い(モジュール化して共有する設計にすると、それぞれが参照する document.getElementById のスコープや呼び出しタイミングの違いを吸収する層が余計に必要になるため、複製の方が変更コストに見合う)。


2026-07-20(テキスト塗りのグラデーション・テクスチャ・塗りなし対応 — フェーズ1)

「レイアウトのテキストツールの塗りにグラデーション・テクスチャを使いたい。塗りなしも線同様チェックボックスで切り替えたい。スタイルモーダルで設定し、Imageタブのテキストでも同様にしたい」との依頼(PLAN_backlog「塗りのグラデーション・テクスチャ・塗りなし対応」フェーズ1)。フェーズ2(レイアウトのドロー図形・Imageタブのシェイプへの展開)は次回作業。

スタイルオブジェクト拡張(後方互換: 未定義=従来動作):

  • fillEnabled(false=塗りなし)/ fillMode(solid/gradient/texture)/ fillGradient(shape: linear|radial・angleDeg・stops[{pos,color}])/ fillTexture(dataUrl・w・h・scale%)。既存の fill(単色)は維持。
  • v2 の「フォントサイズ100pxあたりの相対値」思想を踏襲し、テクスチャのタイルサイズは 画像実寸×(scale/100)×(fontSize/100)。SVG(レイアウト)と Canvas(Imageタブ)で見た目が一致する。

実装:

  • static/js/text-style-modal.js: 「塗り」チェックボックス+モードセレクト+グラデーションパネル(線形/円形・角度・カラーランプ。ランプはImageタブFillツールの _drawFillGradientRamp を移植し、ストップのドラッグ移動・クリック選択・追加/削除・色変更に対応)+テクスチャパネル(画像ファイル選択→最大512pxへ縮小してdataUrl保持(localStorage容量対策)+スケール%)。ランプのストップ等はDOM入力で表現できないためモーダル内 fillState として保持し、getStyleFromUI/applyStyleToUI/applyInitialStyle/resetStyleUI に組み込んだ。
  • static/js/main/09e-text-tool.js: _fontMgrApplyFillPaintToEl() を新設し _fontMgrApplyStyleAttrsToTextEl から使用。defs に linearGradient(objectBoundingBox、角度→x1/y1/x2/y2変換)/ radialGradient / pattern(userSpaceOnUse、data-ccc-tex-w/h/scale でラウンドトリップ用の元データを持たせる)を生成して fill=url(#id)。前回適用分は dataset.styleFillId で管理し再適用時に除去(styleFilterId と同じライフサイクル)。_fontMgrExtractStyleFromTextEl は none / url(#...) を判別してグラデ・テクスチャ・塗りなしを復元する。
  • static/js/main/07-pages.js: _collectReferencedFilters を filter に加え fill / stroke の url(#...) 参照(linearGradient/radialGradient/pattern)も取り込むよう一般化。コマ/オーバーレイ保存・オブジェクトのコマ間移動/複製・テキスト→PNG変換のすべてで定義が持ち回られる(12-text-png-export.js は defs 全体をクローンする方式のため対応不要だった)。
  • static/js/image-tab.js: _rerenderTextLayer の塗りパスを _textFillStyle()(createLinearGradient=バウンディングボックスの角度方向投影幅/createRadialGradient=中心から対角半径/createPattern=repeat+DOMMatrix.scale)に差し替え。fillEnabled=false は fillText パスをスキップ(線・袋文字のみ描画)。テクスチャ画像は _getTextureImage()(dataUrl→Imageキャッシュ)で管理し、未ロード時は単色フォールバック→ロード完了で自動再描画(フォントロードと同じパターン)。_fontStyleAttrsFromStyle/getSelectedTextStyleInfo に新フィールドを追加。
  • static/js/main/19-font-manager.js: フォントタブのスタイル編集UIは単色のまま。_fontMgrEditingFillExt() で編集中スタイルの拡張塗りフィールドを保存時・プレビュー時に持ち回り、フォントタブで再保存しても拡張塗りが欠落しないようにした。CSSミニプレビューはグラデ=先頭ストップ色で近似・塗りなし=透明。
  • i18n: 塗りモード・グラデーション・テクスチャ関連の3言語キーを追加。

検証(Kapture): モーダルで赤→青・角度90°のグラデーション設定→プレビュー・挿入・保存SVG・リロード後の保持を確認。塗りなし+線で fill="none"+旧定義のクリーンアップを確認。テクスチャ(市松模様32px)で pattern 生成・タイルサイズのフォントサイズ比例(フォント529×scale100% → タイル169.28)・抽出ラウンドトリップを確認。Imageタブでグラデ/塗りなし+線/テクスチャの3種を挿入し描画を視覚確認。コンソールエラーなし。

How to apply(SVGとCanvasの塗り表現を揃える): SVGの objectBoundingBox グラデーションに合わせるには、Canvas側は「テキストボックスの角度方向への投影幅」(|cosθ|×W+|sinθ|×H)でグラデーション区間を取ると一致する。テクスチャは両側とも「サイズ相対のタイル寸法」を共通式にすることでズレを防ぐ。SVG側のペイント定義はフィルタ定義と同様に dataset でIDを持たせて適用時に前回分を除去し、保存側は url() 参照の走査(fill/stroke/filter)で defs を持ち回るのが定石。


2026-07-20(CDN 依存ライブラリのローカル同梱: jsPDF/JSZip をオフライン対応に)

DEVLOG 2026-07-17 で「ヘルプにオフライン制限を明記」として先送りしていた、jsPDF/JSZip の CDN 依存を解消した(PLAN_backlog の次回作業)。これによりインターネット接続のないオフライン環境でも PDF/EPUB 出力・zip 保存・一括バックアップ/復元が動作する。

実装:

  • cdnjs 配布物の jspdf.umd.min.js(jsPDF 2.5.1、MIT)と jszip.min.js(JSZip 3.10.1、MIT/GPLv3 デュアル)を static/js/vendor/ に同梱。cdnjs API(https://api.cdnjs.com/libraries/<name>/<version>?fields=sri)の SRI(SHA-512)ハッシュとローカルファイルのハッシュが一致することを検証し、配布物そのまま(改変なし)であることを確認した。ライセンス表記は各 min.js 先頭のヘッダーコメントに含まれる。
  • templates/index.html の <script> 2本を cdnjs URL → /ccc_static/js/vendor/... に差し替え。静的配信は既存の app.router.add_static("/ccc_static", STATIC_DIR)(py/ccc.py)がサブフォルダごと配信するためサーバー側の変更は不要。
  • static/js/vendor/README.md にバージョン・ライセンス・取得元 URL・SRI 検証手順(更新時の手順として)を記録。.gitignore に vendor を除外する記述はなく、min.js はコミット対象。
  • @imgly/background-removal(BG Remove 軽量モデル)は CDN 依存のまま残した。バンドルJSに加えモデルデータ(数十MB)の同梱が必要でリポジトリが肥大化するため。ローカルの背景除去は BiRefNet 連携(comfyui-mask-editor-one)で既にカバーされており、対応可否は PLAN_backlog に「要判断」として整理。

検証: リロード後にスクリプトがローカルパスから読み込まれ CDN 参照が消えていること、window.jspdf.jsPDF / window.JSZip(3.10.1)のロード、jsPDF での PDF 生成(正常な dataURI 出力)、JSZip での zip 生成→読み戻し(内容一致)、コンソールエラーなしを確認。

ヘルプ「ページ — 出力 > オフライン環境について」(3言語)を「全出力形式がオフラインで利用可。Webフォント未キャッシュ時は代替フォント描画の可能性あり。BG Remove(軽量モデル)と Nanobanana は要ネット接続」に書き換え、README 3言語(出力の機能記述+アーキテクチャのディレクトリ構成に vendor/ 追記)を更新した。

How to apply(CDNライブラリの同梱): min.js を static/js/vendor/ に置いて <script> の src を差し替えるだけでよいが、取得物が改変されていないことを cdnjs API の SRI ハッシュ照合で必ず検証し、vendor/README.md に出所・ライセンス・更新手順を残すこと。動的 import 型(esm.sh の @imgly 等)はモデルデータ等の外部フェッチを伴う場合があり、src 差し替えだけでは完結しない点に注意。


2026-07-20(カスタムフキダシSVGの配置後 fill・stroke 変更)

ヘルプ「付録: フキダシSVG仕様」に明記していた既知の制限「配置後にアプリ内でfill・strokeを変更する機能は現状未実装」を解消した(PLAN_backlog の次回作業 1)。

原因(なぜ変更できなかったか): アセット(assets/speech/ 等)のSVGは handleInsertAsset(02-assets.js)経由で <image href="data:image/svg+xml;base64,..."> として配置される。組み込みフキダシ(balloon-shape の <g>+path)と違い中身がSVG要素としてDOMに存在しないため、既存の色変更UIが届かなかった。

実装: <image> のまま、href内のSVGテキストを書き換えて差し替える方式(既存の画像操作・ハンドル・保存経路を全て流用できる)。

  • static/js/main/08-panels-images.js: _isSvgImageEl()(href が data:image/svg の inserted-image 判定)/getSvgImageColors()(代表色の取得)/applySvgImageColors()(色一括置換→base64再エンコード→href差し替え、dataset.fillColor/strokeColor へ記録)を追加。renderImageHandles() で選択時に塗り/枠ピッカー(レイアウト・セリフ両タブ)を現在色に同期。
  • static/js/main/09a-balloon-init.js: initBalloonManager 末尾で box-color / border-color 系4入力にフックを追加。state.selectedShapeId があればフキダシ優先(従来動作)、なければ選択中のSVG画像へ適用。input で即時反映、change で savePanelSvg(オーバーレイは g[data-overlay-layer] 判定で __overlay__)。
  • 色の適用ルール(要決定だった方針): 「明示指定のない要素にのみ適用」だと典型ファイル(各要素に fill/stroke 明示、またはCSSクラス指定)で何も変わらないため不採用。実効値が none 以外の要素の fill / stroke を一括置換とした。none(穴・透明)と url() 参照(グラデーション等)は維持。
  • CSSクラス色指定への対応: CorelDRAW出力(org_sp1.svg 等)は <style> の .fil0/.str0 で色指定している。SVGを document に一時追加(position:fixed 画面外、同期で即削除)して getComputedStyle で実効 fill/stroke を解決し、CSSクラスより優先される inline style で上書きする。
  • 枠線太さ(stroke-width)の配置後変更は対象外(SVGごとにviewBox座標系が異なり、ページ単位の太さ指定と整合しないため)。ヘルプにその旨を明記。

Kapture検証: org_sp1.svg をオーバーレイに挿入→選択でピッカーが実色(#fefefe/#000000)に同期→塗りピンク・枠青へ変更が即時反映→リロード後も保持、コンソールエラーなし。ヘルプ(フキダシSVG仕様の3言語)・README 3言語のフキダシ機能記述を更新した。

How to apply(<image> として配置されたSVGの編集): dataURLのSVGは「デコード→DOM操作→再エンコードしてhref差し替え」で、ラスター化せずに配置後編集ができる。CSSクラス・継承を含む実効スタイルの判定が必要な場合は、documentへの一時追加+getComputedStyle で解決し、上書きは属性ではなく inline style で行う(属性はCSSクラスに負けるため)。同期処理内で追加→削除すれば描画への影響はない。


2026-07-19(出力メタデータ全形式対応・プロジェクト一括バックアップ・解像度指定の出力サイズ自動計算)

既存ライブラリ(jsPDF/JSZip)で追加可能な機能として調査済みだった「PDF出力のメタデータ設定」「JSZipによる一括バックアップ」の実装依頼を受け、レビューを経て「メタデータの全形式対応」「解像度指定による出力pxサイズ自動計算」まで拡張した。あわせてアプリ名表記を Eagle Comic Creator → ComfyUI Comic Creator に統一した(ComfyUIカスタムノード化に伴う改名。eagle_settings 等の機能キーは互換性のため据え置き)。

出力メタデータ(ページタブ→出力サブタブ):

  • 「メタ情報」入力行(タイトル・著者・件名・キーワード)を全形式共通で常時表示。入力値は ccc_export_meta へ永続化(旧 ccc_pdf_meta から自動引き継ぎ)。空欄項目は未設定、全欄空なら一切加工しない。
  • PDF: pdf.setProperties() で Info 辞書へ。jsPDF 2.5.1 の putInfo は値をリテラル文字列として素通しするため、日本語はBOM付きUTF-16BEのバイト列に自前エンコードする _encodePdfInfoText() を実装(CDNソースの putInfo を確認して確定)。
  • EPUB: content.opf の Dublin Core(著者→dc:creator、件名→dc:description、キーワード→カンマ/読点分割で dc:subject 複数展開、XMLエスケープ付き)。
  • 画像: 新規ファイル static/js/main/13a-export-metadata.js に集約し、handleExport の toBlob 直後の1箇所で _embedImageMetadata() を通す(単発保存・フォルダ保存・zip保存すべてに効く)。PNG=iTXtチャンク(IHDR直後に挿入、CRC32自前実装)/JPEG=XMPのAPP1セグメント(既存APPn群の直後)/WebP=VP8Xヘッダ新設+XMPチャンク(VP8Lのアルファビットを検出してALPHAフラグ継承、RIFFサイズ更新)。失敗時は元Blobを返すフェイルセーフ。
  • Nodeでの構造検証23項目(PNG全チャンクCRC再検証・iTXt/XMP読み戻し・RIFFサイズ整合・チャンク順・空メタ無加工)を全パス。

プロジェクト一括バックアップ(ページタブ→作品管理サブタブ):

  • 「バックアップ」で IndexedDB 全ストア(pages/templates/trash/settings)と本アプリのlocalStorageキー23個を ccc_backup_日時.zip へ書き出し(11a-work-manager.js)。ComfyUIと同一オリジンでlocalStorageを共有しているため、全キーダンプではなく _BACKUP_LS_KEYS の明示列挙にした(復元時も同リストでフィルタ)。
  • 「復元」は backup.json マニフェスト(format識別子)検証→件数入り確認ダイアログ→マージ書き戻し(同名上書き・他は保持、settingsストアのみkeyPathがid)→自動リロード。

解像度指定による出力サイズ自動計算(出力サブタブ):

  • 「解像度」セレクト(手動/72〜600dpi)を追加。作品サイズ(内部単位=mm×100)から mm×dpi÷25.4 で幅・高さを自動計算。基準作品は「選択ページの所属作品→フィルタ中の作品→アクティブ作品」の順で解決(画像取込ページはpx実寸を持つため、ページのwidth/heightは基準に使わない)。
  • 幅・高さの手動編集でセレクトは「手動」へ自動復帰(プログラム的代入ではinputイベントが発火しないことを利用)。dpiは ccc_export_dpi に永続化し、起動時・ページ選択時は silent モードで再適用。
  • 出力px上限を 3000→8000 に引き上げ(_EXPORT_MAX_SIZE。従来上限では300dpi A4=3508pxすら不可)。超過時は縦横比維持で縮小し通知。
  • PDFの物理サイズ修正: px→mm換算を固定96dpiから選択dpiに変更。A4作品を300dpiで出力するとPDF用紙が210×297mmになる(手動時は従来どおり96dpi)。

ヘルプ(出力・作品管理の3言語)、README 3言語の機能一覧、i18n(メタ情報・バックアップ・解像度の3言語キー)を更新した。

How to apply(ツール経由のコード書き込みでバックスラッシュ列が化ける): 今回 /[^\x00-\xff]/ という正規表現をEditツール経由で書いたところ、ファイルに生のNUL/0xFFバイトが混入した(シェル・ツールのエスケープ変換が原因。Bash heredoc経由のperl/pythonでも \\x00 が \x00 に潰れて修正が空振りした)。JSソースに \xNN/\uNNNN リテラルを書く必要がある場合は、エスケープ不要な等価コード(codePointAt() 比較や String.fromCharCode(0xNN))に書き換えるのが確実。バイナリレベルの修正はPythonの bytes([0]) のような数値構築で行い、書き込み後は必ず xxd 等でバイト検証すること。

How to apply(ブラウザ内アプリのバックアップ設計): 同一オリジンに他アプリ(ComfyUI本体)が同居する場合、localStorageの全キーダンプは他アプリの設定を巻き込むため、自アプリのキーを定数リストで明示列挙し、復元側も同リストでフィルタする。復元はDBのkeyPath(name/id)ごとの存在チェック+マージ方式にし、完了後はリロードで全タブ状態を作り直すのが安全。


2026-07-18(画像リサイズ: Alt+ドラッグで縦横比固定を解除できるように)

レイアウトタブの画像ハンドルリサイズについて「現在は縦横比固定だが、Alt+ドラッグで固定解除したい」との要望を受けた。

調査で判明した実態: リサイズ処理(static/js/main/08-panels-images.js の imgResizing ブロック)は実は枠(x/y/width/height)を自由変形しており、縦横比を固定するコードは存在しなかった。「固定に見えていた」原因は、<image>要素にpreserveAspectRatioが未指定のためSVGデフォルトのxMidYMid meet(縦横比維持で内接)が効いており、枠を歪めても画像の中身は歪まず表示されていたため。つまり従来は「枠だけ歪み、枠と画像の間に見えない余白が育つ」状態だった。

実装: mousemoveイベントのe.altKeyで分岐する2モードに書き換えた。

  • 通常ドラッグ: 枠ごと縦横比固定でリサイズ。角ハンドルは伸び率の大きい方の軸に追従した等倍スケール、辺ハンドルも等倍スケール(直交方向は中央固定)で、反対側の辺/角が固定点。見えない余白が発生しなくなる。
  • Alt+ドラッグ: 従来の自由変形ロジックをそのまま使用し、加えてpreserveAspectRatio="none"を画像に設定して中身も枠に合わせて実際に伸縮させる。属性は保存されるため、以降その画像は枠どおりに表示される。ドラッグ中のAltの押し離しでモードが即時切り替わる。

過去に辺ハンドル操作で「枠だけ歪んで中身に余白がある」状態になっていた既存画像は、Alt変形した瞬間に中身が枠いっぱいに引き伸ばされ見た目が変わる(枠と中身のずれの解消として許容)。ヘルプ「画像の挿入」・README 3言語の「画像配置」にAltの説明を追記した。

How to apply(SVG imageの「縦横比が固定されて見える」現象): SVGの<image>はpreserveAspectRatio未指定だとxMidYMid meetがデフォルトで、width/height枠を歪めても中身が歪まない。「リサイズが縦横比固定になっている」ように見える挙動を調査するときは、リサイズ計算のコードだけでなくpreserveAspectRatioの有無を必ず確認すること。自由変形をサポートする場合はnoneの明示が必要。


2026-07-18(マンガ効果: 集中線を参考アプリ移植で品質改善、ウニフラ/ウニ(輪)追加、ヴィネット改善)

マンガ効果モーダルの集中線について「参考アプリ(manga-halftone-processor)と比べ質が悪い」との報告を受けた。Kaptureで両アプリの表示を見比べ、参考実装(HalftoneCanvas.tsx)との差分を特定した。

指摘された問題と原因:

  1. 線の奥の先端が太い — 旧実装は線の終端を対角長の50〜100%のランダム位置で止めていたため、太い側の終端がキャンバス内に見えていた。参考アプリは線を常に対角長いっぱいまで伸ばすので太い終端は必ずキャンバス外に出る。
  2. 密度最大でも手前側に隙間 — 旧実装は本数最大120本+終端がランダムに途切れる方式。参考アプリは密度%×5本(最大500本)で全線が外周まで届く。
  3. 線の長さが変えられない — 旧実装は中心の空白が固定だった。

実装(static/js/main/15b-manga-tone.js):

  • _mangaDrawRadialSpeedLinesを参考アプリの移植版に全面書き換え。中心側の先端は針状(0.2px相当固定)、外枠側だけが太くなるテーパー四角形で、常に対角長まで描く。密度は%指定(本数=密度×5、最低15本)に変更。「範囲・長さ」(中心の空白サイズ)・「外枠側の太さ」スライダーを新設(i18n 3言語追加)。
  • 手前側の塗りつぶし対応: 「外枠側の太さ」上限を15→60pxに拡張。太さを上げると隣接する線が外側で重なり、手前側がベタ塗りになる(密度・長さ・太さの組合せで集中線〜ウニフラッシュ状のベタ表現まで1つのモードでカバー)。
  • ウニフラ/ウニ(輪)を新規追加: 参考アプリのdrawUniFlash(中心コア円+短中長3層の外向き三角トゲ、密度×5本)とdrawUniRing(基準円の内外に伸びる細ストローク束、密度×9.5本、線幅ランダム)を移植。ウニ系専用の「外側の長さ」スライダーを追加。参考アプリはウニフラ白固定だが、本実装では既存の色設定を適用する仕様にした(透過オブジェクトのため背景に応じて白/黒を選ぶ)。
  • ヴィネット改善(「効果があまりない」との指摘対応): 円形グラデーション(半径=対角長)だと最大濃度に達するのが四隅だけで四辺中央がほぼ暗くならなかった。キャンバスの縦横比に合わせた楕円グラデーション(ctx.scale変形)に変更し、中間色停止点(55%地点で濃度×0.55)も追加して効きを強くした。
  • サイズ系パラメータはすべて_MANGA_SIZE_REFERENCE_DIM(400)基準のsizeScaleでスケールし、プレビューと適用結果の見た目を一致させている(前回確立した設計指針に準拠)。

Kapture検証: モーダルのプレビューcanvas(閉じられた後は一時canvasをbodyに追加→確認後削除)へ新ロジックを直接描画し、密度100で外周まで隙間なし・太さ40で手前ベタ塗り・ウニフラ/ウニ(輪)が参考アプリ同等の見た目になることをスクリーンショットで確認した。

How to apply(「質が悪い」系の報告は参考実装と両方の実物を見る): 見た目の品質差の報告は言葉だけで原因を推測せず、参考実装のコードと、可能ならKapture等で両方の実際の描画結果を並べて確認する。今回は「終端を対角長まで伸ばす(太い端をキャンバス外に出す)」という参考実装の設計意図がコード比較で初めて分かった。移植時はパラメータのスケール基準(絶対px か 基準解像度比か)の変換を忘れないこと。


2026-07-18(マンガ効果モーダル: プレビュー背景3択追加・Opacity/Intensity削除・背景画像の縦横比修正)

ハーフトーンモーダルで確立した「選択画像/デフォルト/白」のプレビュー背景3択UIを、マンガ効果モーダルにも展開した。

  • プレビュー背景3択: _mangaGetRegionBackdropImage()(選択中の画像→対象コマ/オーバーレイ内の既存画像の優先順)を再利用し、背景ガイド画像がある場合は「選択画像」を初期選択・ない場合はボタン非表示。背景はプレビュー確認専用で生成される透過オブジェクトには含まれない。
  • Opacityスライダー削除: 不透明度は挿入後にレイヤーパネル側で調整する方式のため、モーダル内のスライダーと挿入時のopacity属性を削除(常に不透明で挿入)。
  • 集中線のIntensityスライダー削除: 純粋な不透明度(globalAlpha)だったためレイヤー側調整と重複。削除して常に不透明で描画(ヴィネット強度・スクリーントーン強度は広がり/粒密度を制御する機能的パラメータのため残置)。
  • 背景ガイド画像の縦横比バグ修正: プレビューcanvas(=コマの縦横比)へdrawImage(img, 0, 0, w, h)で全面に引き伸ばしていたため画像が歪んでいた。縦横比を保った中央トリミングのカバー描画ヘルパー_mangaDrawBackdropCover()を新設し、ハーフトーン「パターンを作成」プレビューとマンガ効果プレビューの両方で使用。

あわせてヘルプ(「マンガサブタブ」「画像サブタブ」のハーフトーン記述)とREADME 3言語の「マンガツール」項目を、集中線4種・プレビュー背景3択・不透明度のレイヤー側調整方式に合わせて更新した。


2026-07-17(ComfyUI Manager PR承認 → READMEにマンガツールを反映 → Registry公開へ)

ltdrdata/ComfyUI-Manager(現 Comfy-Org/ComfyUI-Manager)への登録PR(#3086、詳細は本ファイル内「ComfyUI Manager PR提出」の項を参照)が承認された。ユーザー方針で待機していたComfyUI Registry(registry.comfy.org)公開のステップ2に進む前段として、README 3言語(README.md/README_en.md/README_zh.md)の「レイアウトタブ」機能一覧に、直近で実装した「マンガツール」(ハーフトーン変換/生成 + マンガ効果、コミット2316da5)の項目が未反映だったため追記した。

追記内容: PixiJS FXの項目の直後に、ハーフトーンモーダル(画像を変換/パターンを作成の2モード)とマンガ効果モーダル(ヴィネット・スクリーントーンノイズ・集中線の透過オブジェクト生成)をまとめた1項目を追加。

How to apply(機能追加時のREADME更新漏れ防止): 新機能をDEVLOGに記録するタイミングと、ユーザー向けドキュメント(README)へ反映するタイミングがずれることがある。ComfyUI Manager PR・Registry公開のような対外公開作業の直前は、直近の機能追加コミットがREADMEに反映済みかを必ず確認すること。

ComfyUI Registry公開: comfyui-registryスキルのステップ2に従い、pyproject.toml(PublisherId="statsu"、既存公開済みノードのcomfyui-vrm-pose-editorから流用して確認)・GitHub Actionsワークフロー(Comfy-Org/publish-node-actionをコミットSHA固定・permissions: contents: read付きで新設)・サムネイル画像(docs/1_top.pngを800×380にリサイズしてdocs/thumb.pngとして新設)を追加してpush。

初回pushはversion = "1.2.0"のままだったが、既存のv1.2.0タグがそれより前のコミット(2316da5)を指しており、タグ付きアーカイブとRegistry公開内容がズレる問題に気づいた。ユーザー確認の上、v1.2.0タグ以降の未リリースコミット(PDF/EPUBオフライン制限のヘルプ追記・今回のREADME/Registry公開準備)を含めてv1.2.1としてパッチバージョンを再公開し、公開に使われたコミットにちょうど一致する形でv1.2.1タグ・GitHub Releaseを作成した。公開URL: https://registry.comfy.org/publishers/statsu/nodes/comfyui-comic-creator

つまずいた点: 初回のワークフロー実行がOption '--token' requires an argumentで失敗した。原因はユーザー側のREGISTRY_ACCESS_TOKEN Secret設定の誤りで、修正後にgh workflow runで手動再実行し成功した(comfyui-registryスキルのトラブルシューティング表に載っている既知のパターンと一致)。

How to apply(バージョンバンプ前にタグ位置を確認): 既存タグがある状態で新規にpyproject.tomlを追加してRegistry初公開する場合、その時点のversion値と同名のGitタグが過去のコミットを指していないか(=今回pushする内容を含んでいないか)を事前に確認すること。含んでいなければ、そのversionのままpushせず、パッチバージョンを上げてから公開し、公開に使ったコミットに対して新しいタグ・Releaseを作成する。


2026-07-17(ヘルプ「ページ — 出力」にPDF/EPUB出力のオフライン制限を明記)

リリース前チェックとして「requirements.txt作成の要否」をユーザーと確認していた過程で、PDF/EPUB出力機能が何に依存しているかを改めて調査した。

調査結果: PDF/EPUB出力(static/js/main/13-export-pdf-epub.js)はComfyUIネイティブの機能ではなく、Pythonライブラリにも依存していない。実体はブラウザ側JavaScriptで、PDF生成はjsPDF、EPUB(ZIP)生成はJSZipを使用しており、両方ともtemplates/index.htmlからhttps://cdnjs.cloudflare.com/...のCDN経由で<script>タグ読み込みしている(ローカル同梱ではない)。そのためrequirements.txt不要という結論自体は正しい一方、インターネット接続のないオフライン環境ではPDF/EPUB出力ボタンが動作しないという制約が新たに判明した(JPEG/PNG/WebP出力は外部ライブラリ不要なため影響なし)。

対応: ユーザーからオフライン環境での利用者を想定してヘルプページに明記してほしいとの依頼を受け、ヘルプタブの「ページ — 出力」セクション(日本語/英語/中国語の3言語すべて)に「オフライン環境について」の項目を追加した(static/js/main/22-help-tab.js)。CDN依存の理由とJPEG/PNG/WebPは影響を受けない旨を記載。

How to apply(外部ライブラリのCDN依存はrequirements.txt確認だけでは見つからない): 「サーバー側の依存関係(requirements.txt)が不要」という結論は、クライアント側JavaScriptがCDN経由で外部ライブラリを読み込んでいないことを保証しない。オフライン配布・オフライン利用を想定するアプリでは、フロントエンドの<script src="https://...">タグも合わせて棚卸しし、CDN依存が見つかった場合はローカル同梱への切替を検討するか、少なくともヘルプ等のドキュメントに制約として明記すること。


2026-07-17(ハーフトーン/マンガ効果: プレビューと適用結果でドット密度が一致しないバグを修正)

ユーザーからスクリーンショット付きで「Generate Patternで作成したハーフトーン画像がプレビューとだいぶ異なる」との報告を受けた。プレビューでは明瞭に見えていたドットパターンが、実際にコマへ適用した結果ではほぼ見えないほど微細になっていた。

原因: dotSize(および _mangaDrawScreentoneTexture の grainSize、集中線の線の太さ)が「canvas上の絶対ピクセル値」として実装されていたが、プレビュー用canvas(プレビュー枠の実サイズ、せいぜい数百px)と実際に適用するcanvas(_MANGA_HALFTONE_MAX_DIM=2400まで許容する大きな解像度)とでは解像度が大きく異なる。同じdotSize=8という値でも、400px幅のcanvasでは画像の2%を占める目立つドットになる一方、2000px幅のcanvasでは0.4%程度の非常に細かいドットになってしまい、プレビューで確認した見た目と適用結果が一致しないという実装上の欠陥だった。

修正: 新設した _MANGA_SIZE_REFERENCE_DIM(400)を基準に、dotSize・スクリーントーンノイズのgrainSize・集中線(放射状/線形)の線の太さを、実際の描画先canvasの長辺サイズに比例してスケールするよう修正した(sizeScale = Math.max(width, height) / _MANGA_SIZE_REFERENCE_DIM)。これにより、プレビューがどんな解像度で描画されようと、また実際の適用がどんな解像度で行われようと、"画像に対する相対的なドット密度"が常に一致するようになった。影響範囲は _mangaRenderHalftone(ハーフトーンの画像を変換/パターンを作成の両モード共通)、_mangaDrawScreentoneTexture、_mangaDrawRadialSpeedLines、_mangaDrawLinearSpeedLines(いずれもマンガ効果モーダル)。

Kapture実機検証: 画像入りのコマで「パターンを作成」モードのプレビュー(選択画像を背景に表示)でドットパターンを確認したのち「適用」し、レイアウトタブの表示倍率を300%→150%まで拡大して実際にコマへ挿入された結果を確認。修正前は肉眼でほぼ判別できないほど細かかったドットが、修正後はプレビューと同等の明瞭な網点パターンとして表示されることを確認した。

How to apply(プレビュー解像度と適用解像度が異なる機能の設計指針): 「軽量なプレビューを小さい解像度で描画し、実際の適用は高解像度で行う」という最適化パターンを使う場合、パラメータの中に「canvas上の絶対ピクセル値」として解釈されるものがあると、プレビューと適用結果で見た目が一致しなくなる典型的な罠になる。この種の機能を実装する際は、サイズ系パラメータは必ず「基準解像度に対する比率」として扱い、実際の描画先の解像度に応じてスケールする設計を最初から組み込むこと(今回のような後追い修正を避けられる)。

リリース: ユーザー承認のもと本セッションの全変更(マンガツール新規実装〜今回のバグ修正まで)をまとめてコミット→git push origin master→マイナーバージョンとしてv1.2.0タグを作成・push→gh release create v1.2.0でGitHub Release公開(https://github.com/ketle-man/comfyui-comic-creator/releases/tag/v1.2.0 、日本語本文+英語summary併記)。新機能(マンガツール一式)を含むためマイナー版(v1.1.0→v1.2.0)とした。


2026-07-17(ハーフトーンプレビュー背景UIを3択「選択画像/デフォルト/White」に統合)

前回の修正(Convert Imageの不透明化バグ修正+背景トグル)に対し、ユーザーから「意図が伝わっていない」と再度フィードバックを受けた。改めて整理すると要点は次の通り:

  • Convert Image・Generate Patternのどちらも、選択した画像を見ながらドットサイズ等を調整しないと出来上がりの具合が判断できない(両モードに共通する本質的な要求)。
  • Convert Imageは「背景切り替えを追加する前は機能していた」——つまり以前の壊れていない状態(用紙色で不透明に変換される)が正しい前提であり、背景暗さによる視認性の悪さだけを「デフォルト/白」で解消したい、という単純な話だった(前回の不透明化バグ修正はこの理解で合っていた)。
  • Generate Patternについては、「パターンの後ろに選択画像/デフォルト/白で切り替えて確認しながら設定したい」——つまり2択(デフォルト/White)ではなく、「選択画像を背景に表示する」を含む3択が必要だった。前回実装した「ガイド画像を表示」チェックボックス+デフォルト/White 2択という2段構えのUIは、機能的には近いものの、ユーザーが求めていた「1つの3択スイッチ」というシンプルな操作感と一致していなかった。

実装: プレビュー背景の選択UIを、mh-preview-bg-group 内の単一セグメントボタン群「選択画像/デフォルト/White」に統合した。「選択画像」ボタンは「パターンを作成」モードかつ背景ガイド画像(選択中の画像、または対象コマ内の既存画像)が利用可能な場合のみ表示し、それ以外(「画像を変換」モード、またはガイド画像が存在しない場合)は自動的に非表示になる。状態はpreviewBgMode('image' | 'default' | 'white')という単一の変数で管理し、モード切替時にupdatePreviewBgButtons()が可視性と選択状態の整合性を取る(「選択画像」が使えなくなったら自動的に「デフォルト」へフォールバック)。「パターンを作成」モードを選択画像ありで開いた場合は、ユーザーの要望通りデフォルトで「選択画像」が選ばれた状態にした(ガイドなしで開始すると調整の基準がないため)。

「画像を変換」モードは引き続き常に不透明で、「デフォルト」(設定中の用紙色)/「White」(プレビュー限定の白上書き)の2択のみ(「選択画像」は変換対象そのものが背景になるため表示しない)。適用結果への影響は従来通り: Convert Imageは常に実際の用紙色で確定、Generate Patternは背景設定に関わらずパターンのみが生成される。

Kapture実機検証: 画像入りのコマを選択(画像自体は未選択)して「パターンを作成」モードを開き、「選択画像」が既定でアクティブになり実際の絵を背景にドットパターンをプレビューできることを確認。「デフォルト」(チェッカーボード)「White」(白背景)への切替も確認。続けて実際に画像を選択して「画像を変換」モードを開き、「選択画像」ボタンが表示されず「デフォルト/White」の2択のみになることを確認。

How to apply(フィードバックの再確認は具体的な操作イメージまで踏み込む): UIの意図がうまく伝わらなかった場合、抽象的な言葉(「背景を切り替えたい」)だけでなく、ユーザーが思い描く具体的な選択肢の構成(今回は「選択画像/デフォルト/白の3択」)まで踏み込んで確認・実装すること。機能的に等価な実装(チェックボックス+2択ボタン)でも、UIの構造がユーザーのメンタルモデルと異なると「意図が伝わっていない」と感じさせてしまう。


2026-07-17(Convert Imageの不透明化バグ修正 + プレビュー背景/ガイド画像の再設計)

前回実装した「Black/White」プレビュー背景トグルをユーザーが実際に使ってみたところ、「これではConvert Imageが機能しない」という指摘を受けた。調査の結果、セッション中に生成モード(Generate pattern)向けに追加したtransparentBackgroundオプションが、意図せず「画像を変換」モードにも漏れ込んでいる実装バグを発見した。

根本原因: readOptionsFromUI()は$('mh-transparent-bg').checkedをモードに関係なく常に読み取る実装だった。このチェックボックスはHTML上checkedがデフォルトで付与されており、#mh-generate-params内(Convert Imageモードではdisplay:none)にあるため、Convert Imageモードではユーザーが触れられないままtrueであり続ける。結果、_mangaRenderHalftone()内のif (!options.transparentBackground) { 用紙背景を描画 }が常にスキップされ、「画像を変換」の実際の出力が意図せず透過(ドットのみ、用紙背景なし)になっていた。これは今セッションの改修(コマサイズ生成モードの追加)で生まれた回帰バグで、本来「画像を変換」は選択画像を完全に置き換える不透明な仕上がりであるべきだった。

修正: 「画像を変換」モードは常にtransparentBackground: falseを強制するようにし(プレビュー・適用の両方)、モード間のオプション漏れを断ち切った。これでConvert Imageは常に用紙色(モノクロ=白、デュオトーン=設定した紙色)を背景に持つ、正しい完全変換画像を生成するようになった。

プレビュー背景トグルを「デフォルト/White」に再設計: 単純なCSS背景色の切り替え(Black/White)ではなく、実際のレンダリングパラメータ(用紙色)を一時的に上書きしてプレビューし直す方式に変更した。「デフォルト」は設定中の実際の用紙色(デュオトーンのカスタム紙色を含む)でプレビューし、「White」はプレビューのみ用紙色を強制的に白に差し替える(実際の適用結果には影響しない、あくまで見やすさ確認用)。デュオトーンで暗い紙色を設定していても、Whiteボタンで一時的に白背景での見え方を確認できる。

Generate Patternモードに「ガイド画像を表示」トグルを追加: 前回実装した「対象コマ内の既存画像をスケール確認用の背景ガイドとして自動表示する」機能に、明示的なON/OFFチェックボックスを追加した(ガイド画像が利用可能な場合のみ表示)。OFF時は「デフォルト/White」設定に応じてチェッカーボード(透過を示す)または白背景にフォールバックする。ガイド表示のON/OFFに関わらず、「適用」時は従来通りパターンのみが生成され、ガイド画像は一切焼き込まれないことをKaptureで確認した。

Kapture実機検証: Convert Imageモードでデュオトーン・暗い紙色を設定し、「デフォルト」で実際の紙色、「White」で強制白にプレビューが切り替わることを確認。Generate Patternモードで「ガイド画像を表示」のON/OFFによりプレビュー背景(実画像⇔チェッカーボード/白)が切り替わり、どちらの状態でも「適用」でパターンのみが挿入されることを確認。

How to apply(モード別オプションの読み取りは明示的に分離する): 複数モードを持つUIで、片方のモード専用のはずのオプション(今回はtransparentBackground)を、共有のreadOptionsFromUI()のような関数で無条件に読み取ると、非表示になっているだけのDOM要素の初期値がもう片方のモードに漏れ込む。モード固有のオプションは、実際に使用する箇所(レンダリング・適用処理)で「今のモードで本当に使うべきか」を明示的に判定してから使うか、今回のように該当モードでは値を強制上書きすることで、DOM状態への依存を断ち切ること。


2026-07-17(ハーフトーンモーダルのUI改善5点: プレビュー拡大・背景トグル・レイアウト固定・Opacity削除)

「Convert Image」モードを実際に使ったユーザーから、モーダルUIの不具合・改善要望を5点受けた。

1. プレビューが小さい/背景を白に切り替えたい: previewTargetSize() が「元画像の実ピクセルサイズを400px以内に収める」計算だったため、元画像が小さいとプレビューcanvasも小さいまま表示されていた(max-width/max-height:100%は上限を絞るだけで拡大はしない)。プレビュー枠(#mh-preview-wrap)の実際のclientWidth/clientHeightを基準に、画像またはコマ領域のアスペクト比を保ちながら枠いっぱいに拡大/縮小するよう変更し、「Generate pattern」モードと同じ見た目の大きさになるようにした。あわせてプレビュー枠の背景色を黒/白でトグルできるボタンを追加(暗い画像は黒背景だと見づらいため)。

2〜4. レイアウト崩れ3点(上部ボタンに戻れない/横スクロール/ボタン下部の見切れ): いずれもモーダル右パネルの構造に起因していた。従来は「モード切替ボタン+全パラメータ」をまとめて1つのoverflow-y:auto領域に入れていたため、Duotone選択などでコンテンツが増えるとモード切替ボタンごと下にスクロールしてしまい(→上部ボタンに戻れない)、<label>がinline要素のままrangeスライダーにwidth:100%を指定していたことで意図しない横方向のはみ出しが発生し(→横スクロール)、.tsm-bodyのインラインoverflow:autoとパラメータ領域のoverflow-y:autoが二重にかかってflexboxの縮小計算を乱していた(→footerが見切れる)。右パネルを「モード切替ボタン(flex-shrink:0で常時固定)」と「パラメータ一式(#mh-params-scroll、overflow-y:auto; overflow-x:hidden;で独立スクロール)」に分離し、各<label>をdisplay:block化・rangeにbox-sizing:border-boxを付与、.tsm-body自体のoverflow指定を削除してmin-height:0によるflex収縮に一本化することで解決した。

5. Opacityコントロールの削除: 「不透明度は挿入後にレイヤーパネルの汎用Opacityスライダーで調整できるため、モーダル内に専用コントロールは不要」との指摘。実際に確認すると、「画像を変換」モードの適用処理(_mangaCommitCanvasToSelectedImage)はoptions.opacityを全く参照しておらず、UIとしては存在するが機能していないパラメータだった(実質的なバグ)。「パターンを作成」モードではextraAttrs.opacityとして機能していたが、一貫性のためモーダルから完全に削除し、生成物は常にデフォルト不透明度で挿入、以後の調整はレイヤーパネルに委ねる設計にした。

Kapture実機検証: Panel内の既存画像を選択してモーダルを開き、「Convert Image」でプレビューが枠いっぱいに拡大表示されること、White背景トグルで見やすい背景に切り替わることを確認。Color modeをDuotoneに切り替えてパラメータが増えても、モード切替ボタンが常に上部に固定表示され、横スクロールが発生せず、Cancel/Applyボタンが常に見えることを確認。Generate patternモードでも同様にレイアウトが崩れないことを確認。

How to apply(モーダル内の固定ヘッダー+スクロール領域パターン): 縦に伸び縮みするパラメータ群と、常時アクセスしたいコントロール(モード切替・タブ切替等)を同じパネルに置く場合は、最初からそれらを別のflexアイテムに分離し、固定側はflex-shrink:0、可変側だけにoverflow-y:autoを持たせること。1つのoverflow:auto領域に両方を混在させると、コンテンツが増えたときに固定したいはずの要素までスクロールで隠れてしまう。


2026-07-17(マンガツール微修正3点: プレビュー背景画像 / マンガ効果のモーダル化 / 集中線Intensity修正)

直前の改修(コマサイズ生成方式への統一)を実際に触ったユーザーから3点の追加改善依頼を受けた。

1. ハーフトーン「パターンを作成」モードのプレビュー背景を実画像に: 透過チェッカーボードだけでは網点の大きさが実際の絵に対してどう見えるか掴みにくいという指摘。新設 _mangaGetRegionBackdropImage(region) が「選択中の画像→無ければ対象コマ/オーバーレイ内の既存画像」の優先順位でプレビュー背景用の画像を探してロードする。実際に生成・挿入されるcanvas自体は変更せず透過のまま — backdrop画像はプレビューcanvasにのみ描画し、スケール確認用のガイド表示に徹する設計にした。

2. 「マンガ」サブタブをメニュー化: 「マンガ」サブタブは今後複数のマンガ関連ツールを追加していく入り口という位置づけのため、これまでインラインでパラメータ一式を表示していた「マンガ効果」(ヴィネット/スクリーントーンノイズ/集中線)を、ハーフトーンと同じ「ボタン→モーダル」形式に統一した。subtab-manga は「✨ マンガ効果」ボタン1つだけのメニューになり、クリックで mangaEffectsOpen() がハーフトーンモーダルと同じ .tsm-overlay/.tsm-dialog パターンでモーダルを動的生成する(ハーフトーンボタン自体はユーザーの要望により「画像」サブタブに残置)。

3. 集中線(放射状)のIntensity修正: 「強度」スライダーが不透明度(globalAlpha)にしか効いておらず、参考アプリでは強度が中心の空白サイズにも影響していたのに現状は変化しない、との指摘。_mangaDrawRadialSpeedLines() の innerR(集中線が始まる中心の空白半径)計算を、完全ランダムから baseInnerR = maxR * (0.02 + intensity * 0.12) を軸にしたばらつきに変更し、intensityスライダーで中心の空白サイズが視覚的に変化するようにした。

副産物のバグ修正: 実装中、_mangaGetRegionBackdropImage() が getPanelLayerSvg()(#image-layer svg を探す、Imageタブ向けのセレクタパターン)を使ったところレイアウトタブのSVGを取得できず、常にチェッカーボードにフォールバックしてしまうバグを作った。Kaptureで実機検証した際にプレビューへ画像が反映されないことに気付き、07-pages.js で頻出する document.querySelector('#layout-preview svg') に置き換えて解決した。

Kapture実機検証: 既存の網点パターンが入ったコマで「パターンを作成」モードを開き、選択中の画像が無い場合でもコマ内の既存画像がプレビュー背景に表示されることを確認(生成結果は透過のまま変わらないことも確認)。「マンガ」サブタブが「✨ マンガ効果」ボタンのみのメニューになり、クリックでモーダルが開いて従来通りの機能一式(ヴィネット・ノイズ・集中線・コマに追加)が動作することを確認。放射状集中線のIntensityを0.1と1.0で比較し、中心の空白サイズが明確に変化することを確認(0.1ではほぼ見えないほど小さく、1.0では大きくはっきり)。

How to apply(セレクタの使い回しは文脈を確認してから): getPanelLayerSvg() のような「特定タブ専用に作られたヘルパー関数」を別の文脈(今回はレイアウトタブ)で再利用する際は、関数内部のセレクタが本当にその文脈のDOM構造と一致しているか確認すること。名前だけでは汎用的に見えても、実装が特定のIDツリー(#image-layer 等)に依存している場合がある。迷ったら、その文脈で実績のある既存コード(今回は 07-pages.js の #layout-preview svg)に合わせるのが安全。


2026-07-17(マンガツールの改修: 「コマサイズのパターン/オブジェクト生成」方式への統一 + パラメータ拡充)

直前に実装した「マンガ」ツールに対し、ユーザーから「ハーフトーン変換にハーフトーンを作成するだけのモードを追加したい(コマのサイズにハーフトーンを作成)。Mangaツールもコマサイズにオブジェクトを作成する形にしたい。パラメータ数が少ない点も含め改善が必要」という改修依頼を受けた。実際の漫画制作でスクリーントーンシート・集中線シートを描画の上に別レイヤーとして貼る使い方に近づけたい、という意図。

方針決定: ユーザー確認の上、①マンガ効果サブタブ(ヴィネット/スクリーントーンノイズ/集中線)は「選択画像への焼き込み」を全廃し「コマサイズの新規透過オブジェクトを生成して挿入」方式に完全統一、②ハーフトーンの新設「パターンを作成」モードは背景を透過にする(デフォルト、チェックボックスで不透明紙色にも切替可)、の2点を確定した。

対象領域(コマ/オーバーレイ)決定とジャストフィット挿入の実装: 新設 _mangaGetTargetRegion() が、選択中の画像があればその画像が属するコマ/オーバーレイを(imgEl.closest('g[data-clip-panel]') で逆引き、15-pixifx-bridge.js の moveSelectedObjectToCenter() と同じ手法)、画像未選択なら state.selectedPanelId/state.selectedOverlay から対象領域の位置・サイズを取得する。新設 _mangaInsertGeneratedToRegion() は生成したcanvasを insertImage()(08-panels-images.js)経由で挿入するが、placement 引数を明示的に対象領域の座標・サイズで渡すことで、insertImage/insertImageToOverlay のデフォルト自動配置(コマ幅基準アスペクト比フィット/ページ40%中央配置)を上書きし、対象領域にジャストフィットさせるのが実装の肝。挿入前後で state.selectedOverlay/state.selectedPanelId を対象領域に合わせて一時的に書き換え、insertImage() 完了後に元へ戻す設計にした(insertImage() 自体は変更せず、呼び出し側で選択状態を対象に合わせるアプローチ)。

副産物のバグ修正: 実装中に insertImageToOverlay()(オーバーレイへの画像挿入)が extraAttrs(不透明度などの追加属性)を受け取らず、insertImage() からも渡されていないことに気付いた。オーバーレイに新規オブジェクトを追加する際に初期不透明度が設定できない不具合になるため、insertImageToOverlay() に extraAttrs 引数を追加し、insertImage() からの呼び出しでも渡すよう修正した(既存の呼び出し元は引数追加のみで影響なし)。

ハーフトーンモーダルの改修: 「画像を変換」(従来方式)と「パターンを作成」(新規)の2モードをセグメントボタンで切替可能にした。パターン作成モードは元画像の代わりに新設 _mangaCreateSyntheticImageData() が生成する一様濃度またはグラデーション(線形/放射状、開始・終了濃度・角度を指定可)のグレースケール画像をハーフトーン生成ロジックのソースとして使う。既存の _mangaRenderHalftone() はソースがどちらでも同じロジックで処理できるため、コア部分の変更は「背景塗りつぶしを transparentBackground オプションでスキップ可能にする」「invert オプションで濃度を反転できるようにする」の2点で済んだ。加えてパラメータを拡充: 両モード共通で明暗反転・不透明度、パターン作成モードのみ濃度・グラデーション設定・背景透過チェックを追加。開くボタンの条件も「選択画像必須」から「画像またはコマ/オーバーレイいずれかがあれば可」に緩和した。

マンガ効果サブタブの全面改修: 「対象: 選択中の画像」を廃止し「対象: 選択中のコマ/オーバーレイ」に変更(_maskTargetLabel() と同様のロジックで表示)。_mangaDrawScreentoneTexture() は元々「既存ピクセルへノイズ加算」実装だったため、透明ピクセル(alpha=0)にはグレー粒をランダム不透明度で新規配置する分岐を追加し、透過キャンバス単体でも粒状ノイズが乗るようにした(grainSize パラメータで粒の粗さも調整可能に)。ヴィネットに色指定、集中線に放射状時の中心位置(X/Y%指定)・線形時の角度を追加。プレビューはチェッカーボード模様で透過部分を可視化。「適用」ボタンは「コマに追加」に変更し、_mangaInsertGeneratedToRegion() 経由で新規オブジェクトとして挿入する。

Kapture実機検証: 画像未挿入のコマを選択→ハーフトーンモーダルが自動的に「パターンを作成」モードで開くことを確認→グラデーション網点パターンがプレビューに正しく表示→「適用」でコマにジャストフィットする新規透過オブジェクトが挿入されることを確認。既存画像を選択→モーダルが「画像を変換」モードで開き、明暗反転オプションが正しく機能することを確認(従来機能の非破壊を確認)。マンガ効果サブタブでコマを選択→「対象: Panel 1」表示を確認→ヴィネットと放射状集中線(中心位置パラメータ含む)を有効化しプレビューに反映されることを確認→「コマに追加」で新規オブジェクトとして挿入されることを確認。すべてリロード後も結果が保持される(IndexedDBへの永続化)ことを確認。コンソールエラーは終始無し。

How to apply(既存の汎用挿入関数を新用途で再利用する際の落とし穴): insertImage()/insertImageToOverlay() のような「配置ロジック込みの汎用挿入関数」を新しい用途(今回は「対象領域にジャストフィットさせる」)で再利用する際は、関数が受け取る全てのオプション引数(今回は extraAttrs)が実装の全分岐(コマ用/オーバーレイ用)で一貫して機能するか確認すること。片方の分岐だけ引数が欠落しているパターンは、テストするまで気づきにくい。

How to apply(「まとめて焼き込む」から「独立レイヤーとして生成する」への設計転換): 画像加工ツールを「選択済みの何かに適用する」方式で作ると、適用前の状態に戻せない・複数effect を重ねづらい・移動やリサイズが個別にできない、という制約が生まれる。実際の制作ツール(スクリーントーン、集中線シート等)が別レイヤーとして重ねる方式を採用している場合、同じ設計(対象領域サイズの透過オブジェクトを生成し、既存のレイヤーシステムに載せる)に寄せることで、ユーザーが後から自由に調整できる柔軟性を安く獲得できる。


2026-07-17(レイアウトタブに「マンガ」ツール追加: ハーフトーン変換 + マンガ効果)

「レイアウトタブに“マンガ”ツールを追加したい。マンガトーンスタジオ(manga-halftone-processor、React製スタンドアロンアプリ)を参考にハーフトーン・マンガ効果機能を実装してほしい」というユーザー依頼を受けて、計画策定→実装→Kapture実機検証まで行った。

参考プロジェクトの調査: manga-halftone-processorは Canvas 2D API のみで実装された網点(ハーフトーン)変換エンジンを持つ。角度を付けて回転させたグリッド(間隔=dotSize)を対角線範囲でスキャンし、各格子点に対応する元画像1pxの濃度(グレースケール化+明るさ/コントラスト補正、任意で2値化)に比例したサイズの図形(dot/line/square/cross)を描く古典的なAMスクリーニングの簡易実装。CMYK版は4チャンネルを別々の角度でレイヤー分割しmultiply合成する本格的な印刷網点分解も実装していたが、今回は用途(白黒漫画のトーン表現)とコスト(4chループで処理が重い)を踏まえてモノクロ/デュオトーンのみ移植することにした。ヴィネット・スクリーントーンノイズ(紙質ノイズ)・集中線(放射状/線形)も同アプリの実装を参考にした。吹き出し・オノマトペ機能は既存の「フキダシ」「テキスト」サブタブと役割が重複するため対象外とした。

UI設計: ユーザー確認の上、ハイブリッド構成にした。①ハーフトーン変換は「画像」サブタブに独立モーダルとして追加(既存の「PixiJS FX」ボタンと同じ「選択画像のdata URL取得→加工→href置換→savePanelSvg/saveOverlaySvg保存」パイプラインを踏襲)。②ヴィネット・スクリーントーンノイズ・集中線は新規サブタブボタン「マンガ」として常設パネルに配置(プレビューcanvas+パラメータUI+「適用」ボタン)。モーダルはtext-style-modal.jsが確立している.tsm-overlay/.tsm-dialog(document.createElementでJS側から動的生成、既存CSSクラスをそのまま再利用)のパターンに倣い、新規CSSを追加せずに実装できた。

実装: 新規ファイルstatic/js/main/15b-manga-tone.jsにコア描画関数(_mangaRenderHalftone、_mangaDrawVignette、_mangaDrawScreentoneTexture、_mangaDrawRadialSpeedLines、_mangaDrawLinearSpeedLines)と、両UIから共有する適用パイプライン(_mangaLoadImage→_mangaProcessToCanvas→_mangaCommitCanvasToSelectedImage)を実装。_mangaCommitCanvasToSelectedImageは15-pixifx-bridge.jsのpixiFxOpenForLayout()と同じisOverlay判定ロジック(svgEl.querySelector('g[data-overlay-layer]')?.contains(imgEl))を再利用し、保存経路の一貫性を保った。プレビューは常時400px以内にダウンスケールしてrAFスロットルで再描画、「適用」時は元画像を2400px以内にダウンスケールしてから処理(SVG <image>側のwidth/height表示サイズ属性は変更しない)。同期処理でUIがブロックしないよう、「適用」クリック時に処理中表示を出してからsetTimeout(fn, 0)で1フレーム後に本処理を実行する設計にした。

Kapture実機検証: 既存テスト作品のコマ画像を選択→「🎨 ハーフトーン」ボタンでモーダルを開き、パターン形状変更(dot→line→dot)でプレビューが即座に再描画されることを確認。「適用」でコマ内画像が実際に網点パターンへ置き換わり、リロード後も結果が保持されている(IndexedDBへの永続化)ことを確認。続けて「マンガ」サブタブでヴィネットON・集中線を放射状に設定するとプレビューに正しく重畳表示され、「適用」でハーフトーン画像の上にヴィネット+放射状集中線が焼き込まれ、こちらもリロード後に保持されることを確認。コンソールエラーは終始無し。

How to apply(モーダルUIの実装方法): このアプリで新規モーダルUIを追加する際は、静的HTMLに書き足すのではなくtext-style-modal.jsのパターン(.tsm-overlay/.tsm-dialog/.tsm-header/.tsm-body/.tsm-footerという既存の汎用CSSクラスを使い、JS側でdocument.createElementにより動的にDOMを組み立ててdocument.bodyに追加する)を踏襲すると、templates/index.html肥大化を避けつつ一貫した見た目を保てる。

How to apply(画像加工系ツールの適用パイプライン): 選択中の画像を加工して置き換える機能(今回のハーフトーン/マンガ効果、既存のPixiJS FX等)を追加する際は、pixiFxOpenForLayout()が確立した「data URL取得→加工→pushHistory()→href置換→isOverlay判定→saveOverlaySvg/savePanelSvg」のパイプラインをそのまま再利用すること。保存経路の重複実装を避けられ、deferThumbによるサムネイル遅延生成などの既存最適化も自動的に効く。


2026-07-17(レイアウトタブの操作重さ改善 + 派生バグ修正 + 「作品を閉じる」機能)

「レイアウトタブの操作が重い」というユーザー相談を起点に、原因調査→改善実装→派生して見つかったバグの修正→新機能追加までを行った長めのセッション。

原因調査: savePanelSvg/saveOverlaySvg(コマ編集の保存を担う実質2関数、83箇所から呼ばれる)が、1操作ごとに「ライブSVGのcloneNode(true)→dbPut内でbuildMergedSvgによるページ全体再構成→_rasterizeSvgThumbによる画像デコード+canvas再描画→renderLayerPanelのDOM全再構築」という重い処理を同期的にフルで実行していたことが主因。加えてドローツールの色/線幅/不透明度がchangeではなくinputイベントに直結しており、ドラッグ中に高頻度発火していた。

実装した改善(Kaptureで実機計測して効果を確認):

  1. inputイベントに直結していたドローツールのプロパティ変更をDOM反映は即時のまま保存だけ300ms debounce化。実機で10回連続のinput発火に対し保存処理が1回だけ実行されることを確認。
  2. dbPutにサムネイル計算を後回しにするdeferThumbオプションを追加し、savePanelSvg/saveOverlaySvgからのみ使用。DB書き込み自体はサムネイル計算を待たず即座に完了し、サムネイルは600ms debounce後にまとめて1回生成される。
  3. pushHistoryのディープクローン(JSON.parse(JSON.stringify(...))、base64画像込みの可能性がある文字列を含む)を、panels配列が常にイミュータブル更新されていることを確認した上で配列の浅いコピーに変更。

非選択コマの低画質表示は効果なしと判明: ユーザー提案で「非選択コマの画像だけ解像度を下げて表示すればズーム操作等が軽くなるのでは」という案を実装(feature/layout-preview-qualityブランチ)。保存データを一切劣化させない安全設計(data-orig-hrefに原本退避→保存直前に必ず復元)で作り込み、Kapture実機でLong Task計測を行ったところ、画像54枚の状態でズーム操作のLong Task合計が高画質時(3870ms)より低画質時(4235ms)の方が悪化するという結果に。原因は、SVGの<image>要素のwidth/height(表示領域サイズ)はそのままでソース解像度だけ下げたため、ブラウザ側のアップスケーリング補間コストが新たに発生し軽量化効果を相殺したため。効果が実証できなかったためmasterにはマージせずブランチのまま保留。

外部プロジェクト調査(dlewissandy/comictools, pedrinho/comic-drawer): comic-drawerは元々手作りCanvas 2D実装で選択/移動/リサイズ/回転を自前実装していたが、Fabric.jsへ全面移行し自前実装コード約2,750行を削除していた(FABRIC_MIGRATION.md)。このアプリの09c-balloon-handles.js等の手作りハンドル描画と同種の課題であり、中長期的な検討材料として記録。comictoolsのlight table実装からは「ホイール操作は300ms debounce、選択解除前に必ずpending書き込みをflushする」という設計方針を確認し、次のバグ修正の着想を得た。

バグ修正1: debounce待ち中のflush漏れ: 上記のdebounce化(300ms)を入れた際、その間にパネル/オーバーレイを切り替えたりページを再読み込みしたりすると保留中の変更が保存されずに消えるという問題が残っていた。_layerDrawFlushPendingSave()を新設し、選択解除の共通処理(_clearObjectSelection)とページ再読み込み(renderLayoutTab、こちらはDBを読む前に保存完了を待つ必要があるためawait)の直前で必ず呼ぶように修正。Kaptureで、色変更直後(300ms未満)に別パネルへ切り替えてからリロードし、DBに正しく保存されていることを実機確認。

バグ修正2: メインタブ切替時に3Dポーズのループが止まらない: 「レイアウトタブで編集を終了できた方が他タブ作業時のパフォーマンスに効果があるか」というユーザー質問を調査する過程で発見。3Dポーズ(comfyui-vrm-pose-editorノードのpose_editor_core.js)のrequestAnimationFrameループはstopLoop()が呼ばれない限り無条件で自己再帰するが、hidePose3DCanvas()(stopLoop()を呼ぶ唯一の経路)はツールペインのサブタブ切替時にしか呼ばれておらず、メインタブ(レイアウト→画像等)の切替では呼ばれていなかった。switchTab()冒頭に、3Dポーズ表示中にレイアウトタブ以外へ移動する場合のhidePose3DCanvas()呼び出しを追加。window.requestAnimationFrameを直接フックして実測し、3Dポーズ表示中は1秒間に61回(約60fps)呼ばれていたのが、タブ切替後は1秒間に0回になることを確認(evaluateツールが一時的に切断されていたため、この検証だけ後から再接続後に実施)。

バグ修正3: ページ番号「- / N」表示の更新漏れ: ユーザーから「レイアウトタブのページが-/7と表示される」という報告を受けて調査。updateLayoutPageNav()は「今開いているページが作業中の作品のページ一覧に含まれない」場合にページ番号側を「-」にする実装だが、ページ管理タブでページを別グループへ移動する処理(pagemgr-move-group-btn・pagemgr-remove-group-btnのハンドラ)がこの表示更新を呼んでいなかったため、移動後も古いページ数のまま「- / 7」の表示が固まっていた。両ハンドラにupdateLayoutPageNav()呼び出しを追加。Kaptureで実際にボタンクリック経由の移動を行い、「7 / 7」→「- / 6」に即座に更新されることを確認(余談:evaluateから直接Setに選択を追加しただけではボタンがdisabledのままで、実UIの選択操作を経由しないとdisabled解除ロジックが動かないことも実機で判明)。

新機能: 「作品を閉じる」ボタン: 上記の3Dポーズ調査で「タブ切替だけではレイアウトタブの巨大なSVG DOMがメモリに残り続ける」ことが分かったため、明示的に閉じる手段としてOCボタンの右隣に追加。closeActiveWork()(11a-work-manager.js)は3Dポーズ・マスク編集・ドロー描画オーバーレイを後片付けしてからstate.activePage/state.activeWorkをクリアし、_workSetActive(null)でlocalStorageのactive_workも消去(次回起動時に自動で開かれないように)。保存データ自体はDBに残るため「開く」でいつでも再開できる。Kaptureで実機計測し、クリックでdomSizeが73,703,347 → 163,516まで減少(巨大な画像埋め込みSVGが実際に解放された)ことを確認。ヘルプタブにも説明を追記。

How to apply(保存処理の重さ): SVGクローン+シリアライズ+DB書き込みのような「1操作ごとに走る重い保存処理」を扱う際は、①inputではなくchange(またはdebounce)で保存をトリガーすること、②debounceを入れる場合は必ず「選択解除・画面遷移・ページ再読み込みの直前に保留中の処理をflushする」仕組みとセットで入れること(本セッションで一度この対策漏れによるデータロスト系バグを作った)。

How to apply(表示軽量化の検証): 「画像の解像度を下げれば表示が軽くなるはず」という直感は、SVGの<image>要素のように表示サイズ(width/height属性)とソース解像度が分離している場合は必ずしも成立しない。実装前に、実際に負荷を再現する条件(本セッションでは画像を意図的に大量複製)を作ってLong Task等で定量比較してから投資判断すること。

How to apply(アニメーションループの後片付け): requestAnimationFrameを使う機能を画面の一部(サブタブ等)に組み込む際は、「そのサブタブを離れる時」だけでなく「その機能を含む画面全体(メインタブ等)を離れる時」の両方で確実に停止処理が呼ばれることを確認すること。片方の経路にしか後片付けがないパターンは見落としやすい。

リリース: ユーザー承認のもとコミット→git push origin master→マイナーバージョンとしてv1.1.0タグを作成・push→gh release create v1.1.0でGitHub Release公開(https://github.com/ketle-man/comfyui-comic-creator/releases/tag/v1.1.0 、日本語本文+英語summary併記)。新機能(作品を閉じるボタン)を含むためマイナー版(v1.0.1→v1.1.0)とした。


2026-07-16(PixiJS FXモーダルのi18n対応)

ユーザーからの「レイアウトタブ・イメージタブのPixiJS FXのi18n化を確認して」という依頼を受けて調査→修正を実施。

調査結果: レイアウトタブ側のブリッジ処理(main/15-pixifx-bridge.js)は既にt()経由で完全対応済みだった一方、以下2点が未対応と判明した。

  1. pixifx.js(レイアウト/Imageタブ共通で使われるPixiJS FXモーダル本体、940行)がファイル全体を通してt()呼び出しが一つも無く、パーティクル種類の選択肢・ラベル・トグルボタン・アラート・キャンバス描画テキストまで全て日本語ハードコードだったため、言語設定を英語/中国語にしてもモーダルだけ常に日本語表示になっていた。
  2. image-tab.jsの_openPixiFx()のトースト文言が英語ハードコードで、既存のimage.pixifxNotLoaded/image.pixifxApplyErrorキーが定義済みなのに使われていなかった(同ファイルの他メソッドはt()を使っているのにこの関数だけ取り残されていたパターン、G'MICブロックの取りこぼしと同種)。

実装: i18n.jsに新規pixifx.*名前空間(52キー)をja/en/zh 3言語に追加し、pixifx.jsの全ハードコード文字列をt()呼び出しに置換。image-tab.jsの_openPixiFx()は既存キーへの置換+不足していたimage.noImageLoaded/image.noActiveLayer/image.pixifxAppliedを新規追加。index.htmlのPixiJS FXボタン(レイアウト/Image両方)は可視ラベルにもdata-i18nを付与し、他の同種ボタン(G'MIC等)と表記を統一(値自体は「✨ PixiJS FX」でブランド表記のため全言語共通)。

検証: node --checkで3ファイルとも構文エラーなし。Node vmでLANGUAGESオブジェクトを読み込み、pixifx.*関連52キーがja/en/zh間で欠落なく完全一致することと、コード内の全t('...')呼び出しキーがja辞書に存在することを機械検証。

How to apply: 複数タブから共通で開かれる大きめのモーダル(今回のpixifx.jsのような外部カスタムノード連携UI)は、実装時にi18n対応を後回しにしたまま放置されがちなので、新規モーダルを追加した際は完成時に必ずi18n対応をチェックリストに含めること。トースト/アラート文言を追加する際は、まず既存の近い意味のキー(image.pixifx*等)が無いか確認してから再利用し、無ければ命名規則(タブ/機能単位のドット区切り)に従って新規キーをja/en/zh同時に追加すること。

リリース: ユーザー承認のもとコミット→git push origin master→パッチバージョンとしてv1.0.1タグを作成・push→gh release create v1.0.1でGitHub Release公開(https://github.com/ketle-man/comfyui-comic-creator/releases/tag/v1.0.1 、日本語本文+英語summary併記)。今回はUI文言の不具合修正のみでAPI/機能変更が無いためパッチ版(v1.0.0→v1.0.1)とした。


2026-07-16(リリース前最終チェック: メモリリーク修正・セキュリティ再監査・スクリーンショット/ファビコン準備)

近日リリースに向けた最終セッション。ページタブのメモリリーク修正、セキュリティ再監査、README整備、スクリーンショット・ファビコン作成をまとめて実施した。

README依存関係の整理: comfyUI-particle-pixijs(実行時に動的import、PixiJS FX機能を提供)とcomfyui-mask-editor-oneを3言語README(README.md/README_en.md/README_zh.md)に追加。当初「依存関係(任意)」テーブルに両方載せたが、コードを確認したところmask-editor-oneは実行時の外部依存ではなく、Imageタブの Mask ツール(MaskEditorOneTools.jsほか)実装時にコードを参考にした移植元(ソース内コメントに"Inspired by"と明記)だったため、テーブルから外しAcknowledgementsのみに整理し直した。「依存関係(任意)」テーブルは「インストールで機能が有効になるもの」に限定する方針。

ページサムネイルの重大メモリリーク修正(9ページ程度の作品を切り替えると "Not enough memory to open this page" でクラッシュ、というユーザー報告を受けて調査): dbPut()(00-db.js)がページ保存時に生成する「サムネイル」の実体が、buildMergedSvg()で挿入画像(base64込み)を全部合成したページ全体のSVGを、リサイズも圧縮もせずそのままdata:image/svg+xmlのURLにしていただけだったことが判明。作品を切り替えるたびにブラウザがフルサイズ画像入りSVGを何枚もデコードし直すことになり、画像デコードキャッシュとGCが追いつかずメモリが枯渇していた。

  • 対応: _rasterizeSvgThumb(svgText, width, height)を新設し、canvas経由で長辺320px・JPEG品質0.75(透過部分は白背景合成)にラスタ化してからdata.thumbへ保存する方式に変更。dbPutを非同期化。
  • 既存データのマイグレーション: _getOrBuildPageThumb(11a-work-manager.js)が、キャッシュ済みthumbが旧形式(data:image/svg+xml始まり)かどうかを判定し、旧形式なら再生成・書き戻す。ページを一度表示するだけで自動的に軽量なJPEGへ移行する。
  • Kaptureで実機確認済み(保存後・タブ切替後も一貫してJPEG形式のサムネイルが維持されること、新規JSエラーが無いこと)。

image-tab.jsのカテゴリ名i18n対応: Imageタブのテキストツール「カテゴリ」セレクトがlocalStorageを直接読むため、予約カテゴリ「お気に入り」がen/zh UIでも日本語の生キーのまま表示されていた問題を修正。_fontCatLabel(cat)ヘルパーを追加し、window._fontMgrCatLabel(main.js側、function宣言なのでwindow経由で呼べる)があれば通す方式(window._fontMgrGoogleListと同じ既存パターン踏襲)。Kaptureで日英切替の実機確認済み(予約カテゴリのみ翻訳され、ユーザー定義カテゴリ名はそのまま表示)。

セキュリティ再監査: リリース前の再チェック依頼を受け実施。このプロジェクトはgitリポジトリではないため/security-reviewがそのままでは動かず、comfyui-comic-creater/配下でgit init→空コミットをbase originとしてpush→現状全体を差分としてdiffベースを作る、という一時リポジトリ手順(レビュー後.git削除)で実施した。同梱予定の素材アセット(assets/配下のsvg/png/rar)は別配布のため.gitignoreへ除外パターンを追加してから対象にした。マルチエージェントで検証し、confidence 8-9/10の2件を検出・修正:

  1. G'MICパス未検証によるRCE(py/ccc.py): POST /api/ccc/local-gmic/settingsがgmicQtPathを無検証で保存し、subprocess.Popen(args, ...)のargs[0]としてそのまま使われていた。認証もCSRF対策も無いため、LAN攻撃者や「ComfyUI起動中に開いた悪意サイト」(Content-Type: text/plainのfetchはCORSプリフライトを回避でき、aiohttpのrequest.json()はContent-Typeを見ずボディをJSONパースするため盲目的書き込みが成立する)からUNCパスを設定させopen_in_gui_b64を叩くだけでRCEに直結する経路だった。_validate_local_exe_path()ヘルパーを新設(_validate_local_urlと同じ並びに配置)し、UNCパス拒否+os.path.isfile()による実在確認を必須化。CCCErrorとして_error_response(e, status=400, key='detail')で返す(同ファイルの既存local-gmic系ハンドラの形式に合わせた)。クライアント側(14-integrations.js)はcatchでe.message(サーバーの具体的な理由)を優先表示するよう変更。curlで実機確認済み(UNCパス→400 exe_path_unc_forbidden、不存在パス→400 exe_path_not_found、実在パス→200 exists:true、空文字→200許可)。
  2. Stored XSS(アセットファイル名未サニタイズ)(py/ccc.py + 02-assets.js): handle_save_group_assetはgroup名はre.sub(r'[\\/:*?"<>|]', '_', ...)でサニタイズするがfilenameには同じ処理がなく(兄弟関数handle_save_image_projectは正しく適用済みで実装漏れ)、_safe_path()はos.path.basename()+トラバーサルチェックのみで</>/"等は素通りしていた。結果がアプリ起動時に無条件で呼ばれるrenderAssetTree()でasset.name/asset.pathをエスケープなしでinnerHTMLテンプレートリテラルに直接埋め込んでいたため、POSIXファイルシステム上では属性脱出→スクリプト注入が可能だった(Windows/NTFSでは</>/"がファイル名に使えないため無効化されるが、ComfyUIはLinux/Docker運用も一般的)。handle_save_group_assetのfilenameにも同じサニタイズを追加し、多層防御として02-assets.js側で既存の_escHtmlヘルパー(21-script-tab.js定義)を使いエスケープしてからinnerHTMLに埋め込むよう変更。

How to apply(メモリリーク): サムネイル・プレビュー用途で画像やSVGをdata URL化する新規実装をする際は、必ず実際にラスタ化・縮小してから保存すること(「SVGだから軽い」とは限らない——中に埋め込まれたラスタ画像の解像度がそのまま効いてくる)。

How to apply(セキュリティ): subprocess.Popen/subprocess.runに渡す実行ファイルパスを設定として受け付ける新機能は必ず_validate_local_exe_path()を通すこと。ユーザー入力由来のファイル名を新規保存するハンドラは兄弟関数のサニタイズパターンを必ず踏襲すること。クライアント側でinnerHTMLにサーバー由来の文字列を埋め込む新規UIは_escHtml(21-script-tab.js)または_esc(19-font-manager.js)を必ず通すこと(このアプリはReact/Angular等の自動エスケープ機構を持たない素のJS+innerHTML実装のため埋め込み側の対策が必須)。

リリース準備: スクリーンショット・ファビコン作成:

  • demo-captureスキルの手順(Kaptureでの撮影+curl.exeでpreview URLを保存)に沿って、英語UIで10枚のスクリーンショットをdocs/配下に作成: 1_top.png〜7_help.png(README記載済みの7タブ)に加え、8_template_create.png(テンプレート作成ウィザードモーダル)・9_wfmgallery.png(workflow studioギャラリータブ)・10_cc_topbar.png(ComfyUIトップバーのCCボタン)を追加し3言語READMEに反映。
  • モーダル単体のクロップはKaptureのselector指定キャプチャでは座標がずれる不具合を確認(elementsで取得したboundsとスクリーンショットの実ピクセル座標が一致しなかった)。フルスクリーンショットを取得し、Pillow(numpy併用)でエッジ検出して実測ピクセル境界を特定→ffmpegのcropフィルタで正確にクロップする方式に切り替えて解決。
  • 作業中に発見: Nanobananaタブの画像プレビューがalt="生成結果プレビュー"という日本語ハードコードのままだった(他のalt属性は無かったため見落とされていたi18n対象)。i18n.jsのapplyI18nToHtml()に[data-i18n-alt]のサポートを新設(data-i18n/data-i18n-placeholder/data-i18n-titleと同じパターン)し、nb.resultPreviewAltキーを3言語追加して対応。
  • ファビコン: ComfyUI(黒地・白"C")ともWorkflow Studio(青地・白"W")とも衝突しない色・同じ「角丸バッジ+白抜きロゴ」の作法を踏襲する方針で6候補("CC"モノグラム×4色+吹き出しモチーフ+コマ割りグリッドモチーフ)をSVGで作成し、Artifactでタブモック付きプレビューページを作ってユーザーに選定してもらった(マゼンタ#dd4592の"CC"モノグラムを採用)。
    • ハマった点: ImageMagickの-background noneでSVG→PNGレンダリングすると1-bitアルファ(透明/不透明の2値)になり、角丸のアンチエイリアス部分が不透明な白として焼き込まれ、favicon表示で「角が白い」症状になった。-background transparentに変更し、PillowでPNG→ICO変換(Image.save(..., sizes=[...]))することで正しいフルアルファのマルチサイズICOを生成し解決。SVG本体にもwidth/height属性を明示(viewBoxのみだとブラウザ間で解釈がブレる場合があるため)。
    • static/favicon.svg(メイン)・static/favicon.ico(16/32/48/64/128/256pxマルチサイズ、レガシー互換)・static/apple-touch-icon.png(180px)を配置し、templates/index.htmlの<head>にlinkタグ3種を追加。

教訓(画像クロップ・透過検証): KaptureのDOM要素boundsとスクリーンショットの実ピクセル座標は必ずしも一致しない(原因未特定、DPRやレンダリングタイミングの可能性)。要素単位の正確なクロップが必要な場合は、フルスクリーンショットを撮ってPillow/numpyでエッジのピクセル値を直接スキャンし実測するのが確実。透過PNG/ICOの検証も同様に、目視やImageMagickの-composite合成では判断を誤ることがあるため(本セッションでも-compositeの結果がおかしく見えるケースがあった)、Pillowでgetpixel()によりアルファ値を直接読むのが最も確実。

GitHub公開(初回リリース): 上記の修正・準備が完了した後、ユーザー承認のもとcomfyui-comic-createrをgit init→初回コミット→gh repo create ketle-man/comfyui-comic-creater --public --source=. --remote=origin --pushでGitHub公開(他の姉妹プロジェクトと同じpublic設定)。.gitignoreにより.env/settings.json/__pycache__等が正しく除外されていることをgit statusで確認してからコミットした。公開直後、3言語READMEの<your-repo-url>プレースホルダーを実URL(https://github.com/ketle-man/comfyui-comic-creater)に置き換え、Acknowledgements内の4つのコンパニオンノード名(comfyui-vrm-pose-editor/ComfyUI-Workflow-Studio/comfyUI-particle-pixijs/comfyui-mask-editor-one)も実在する公開リポジトリへのリンクに変更する追いコミットをpush。

How to apply(今後の公開作業): このプロジェクトのようにgitリポジトリ化されていない状態からGitHub公開する際は、git init直後に必ずgit statusで除外ファイル(APIキー・ローカルパス設定・実行時生成物)が意図通り除外されているか確認してからコミットすること。公開設定(public/private)は不可逆に近い意思決定のためユーザー確認必須。

GitHub Release作成: リポジトリ公開直後はgit tag/gh releaseをまだ作っておらず「Releases」タブが空の状態だった(git pushしただけではGitHub Releaseは作成されない)ため、ユーザー指摘を受けて追加対応。git tag -a v1.0.0 -m "..." → git push origin v1.0.0 → gh release create v1.0.0 --title "..." --notes "..."で作成(https://github.com/ketle-man/comfyui-comic-creater/releases/tag/v1.0.0 )。リリースノートは機能一覧をREADMEから要約し、日本語本文+英語summaryを併記。

ComfyUI Manager PR提出: comfyui-registryスキルのステップ1に従い、ltdrdata/ComfyUI-Managerをフォーク済み(ketle-man/ComfyUI-Manager)→上流に同期→シャロークローンでブランチ作成→custom-node-list.jsonの既存ketle-manエントリ群(comfyui-mask-editor-one等)の直後にPythonの文字列置換で新規エントリを挿入(json.dumpでの全体再フォーマットは差分が巨大になるため避けた)→JSON妥当性をjson.loadで検証→PR作成。PR: https://github.com/Comfy-Org/ComfyUI-Manager/pull/3086 。ユーザー方針でPR承認をしばらく待ってからComfyUI Registry(registry.comfy.org)公開のステップ2に進む(pyproject.toml新設・GitHub Actions・REGISTRY_ACCESS_TOKENのSecret登録が必要、詳細はcomfyui-registryスキル参照)。

How to apply(Manager PR): custom-node-list.jsonのような巨大JSONに1エントリ追加する際は、既存の同一authorのエントリ群の直後に文字列置換で挿入すると差分が最小かつ可読性も良い。挿入後は必ずjson.loadで構文検証してからコミットすること。


2026-07-15(requirements.txt要否調査 + README/LICENSE/docs新設、3言語対応)

requirements.txt調査: py/ccc.py・py/config.pyの全importを洗い出し、標準ライブラリ/ComfyUI本体同梱済み(aiohttp, PIL)/ComfyUI内部モジュール(server, folder_paths)のいずれかであることを確認。唯一のサードパーティ任意依存jinja2もImportErrorを捕捉するフォールバック実装済みな上、templates/index.htmlにJinja構文({{ }})が一切存在せず現状無意味な依存であることが判明。同環境のcomfyui-workflow-studio(本当に追加依存があるノード)のrequirements.txtと比較し、comic-createrには追加依存が無くrequirements.txt不要と結論。

README/LICENSE/docs新設: comfyUI-mask-editor-oneのREADME構成(README.md/README_en.md/README_zh.mdの3ファイル分割、docs/フォルダへのスクリーンショット配置、機能一覧→インストール→任意設定→使い方→スクリーンショット→アーキテクチャ→ライセンスの章立て)を参考に、comic-creater初のREADME一式を作成。

  • ライセンスはユーザー確認の上MIT(Copyright: Statsu)。LICENSEファイルを新設
  • gitリポジトリ未作成・GitHub未公開のため、「ComfyUI Manager経由インストール」節は<your-repo-url>のプレースホルダーとし、実在しないURLを記載しないよう配慮(comfyui-vrm-pose-editor/ComfyUI-Workflow-Studioへの言及も実URL不明のためリンク無しの太字表記に留めた)
  • docs/フォルダを新設(空、スクリーンショットはユーザーが別途配置予定)。README内ではdocs/1_top.png〜docs/7_help.pngの連番命名でプレースホルダー参照
  • 機能一覧はこれまでのセッションで実装・翻訳してきたヘルプタブ内容(レイアウト/Image/フォント/Nanobanana/スクリプト/外部連携)を基に、README向けに簡潔な箇条書きへ再構成

How to apply: README内で言及する外部リポジトリ(コンパニオンノードや自身のリポジトリ)のURLが未確定の場合は、実在しない/推測のURLを記載しないこと(プレースホルダーは<...>形式か、リンクなしの太字表記に留める)。公開・URL確定後にプレースホルダー2箇所(インストール節・Acknowledgements節)を更新すること。requirements.txtの要否は「ComfyUI本体が保証する依存かどうか」で判断でき、同環境の他カスタムノードのrequirements.txtと見比べる(aiohttp/Pillow/numpy等は書かない、が典型パターン)。


2026-07-15(i18n Phase 4完了: G'MIC/Eagle/Nanobananaのバックエンドエラーメッセージを多言語化)

前回セッションで見積もった「方式1(エラーコード方式)」でPhase 4に着手・完了した。バックエンドはユーザー向け文言を持たず機械可読なerror_codeのみを返し、表示文言は既存のi18n.jsのt()で組み立てる設計。

スコープの絞り込み: 当初py/ccc.py内には日本語/英語混在のユーザー向け文言が多数見つかったが、実際にフロントエンドまで到達し画面に表示されるものだけに対象を絞った。調査の結果、以下が判明:

  • handle_proxy_gmic/handle_gmic_start_server(リモートG'MICサーバー用プロキシ)は、現行フロントに呼び出し元が一切なく完全に到達不能なデッドコード(/api/ccc/gmic/*をfetchする箇所がゼロ)。翻訳対象から除外。
  • saveToEagle()は現状data.status === 'success'の真偽値だけを返し、バックエンドのmessageは握りつぶされて画面に一切出ていなかった(Eagle接続エラー等は事実上サイレント失敗)。合わせて修正(後述)。
  • アセット/グループ/プロジェクト保存系ハンドラの「〜field required」「invalid folder」等は、フロントが常に正しいデータを送るため通常操作では到達しない防御的ガードのため対象外。

実装(py/ccc.py): CCCError(code, message, **params)例外クラスと_error_response(e, status, key)ヘルパーを新設。messageフィールドは日本語のまま残し(サーバーログ・APIデバッグ用、ja基準というプロジェクト全体の方針を踏襲)、error_code/error_paramsを追加するだけの最小差分にした。対象14箇所: URL検証3種(_validate_local_url)、G'MIC Qt未設定・キャンセル・画像必須・ジョブ未検出・結果パス必須・無効パス(×2)・結果ファイル未検出、Eagle接続エラー・ファイル未検出、Nanobanana APIキー未設定(×2箇所)。G'MICジョブの進行中メッセージ(「編集中...」「起動中...」)にもmessage_codeを追加し、_gmic_jobs辞書・handle_local_gmic_statusのレスポンスにerror_code/error_params/message_codeを含めた。

実装(フロントエンド): i18n.jsにerr.*名前空間で15キー×3言語(45文字列)追加し、resolveBackendError(errorCode, errorParams)ヘルパーを新設(error_codeのsnake_caseをerr.xxxCamelCaseキーに変換しt()へ委譲、paramsはオブジェクトの値を位置引数として展開)。呼び出し側はresolveBackendError(...) || 既存の日本語フォールバックの形でチェーンし、コードが無い(=未対応の例外)場合は自然に既存の多言語化済みフォールバック文言に落ちる設計。修正箇所: 14-integrations.js(gmicWaitForJobのstatus.error/message、gmicOpenGui/gmicInsertResultのerr.detail、saveToEagleの戻り値をbooleanから{ok, message}に変更)、image-tab.js(同型の重複G'MIC実装_gmicWaitForJob/_gmicOpenGui、_saveToEagleの呼び出し元更新)、nanobanana.js(生成失敗時のエラー表示)。

検証: Python構文チェック(py_compile)、JS構文チェック(node --check、i18n.js/image-tab.js/nanobanana.js/14-integrations.js)、Node vmでpy/ccc.pyが発行する全15 error_code(grep CCCErrorで洗い出し)がja/en/zh全言語のi18nキーとして存在することを機械検証(不足ゼロ)、resolveBackendError()をNode上で実際に呼び出しparams付き/無し・言語切替・未知コードのフォールバック動作を確認。ただしPythonコードの変更はComfyUI再起動後に反映されるため、実機(G'MICダイアログ・Eagle接続エラー等の実表示)確認はユーザー再起動後に持ち越し。

残タスク: なし(元々のPhase 4スコープは完了)。ただし調査中に副次的に発見した既存の問題2件は今回のスコープ外として見送った: (1) image-tab.jsのG'MICセクション(_gmicOpenGui/_gmicApplyResult)は元々多数のトースト文言がハードコード英語のままでt()化されておらず、Phase 2④(Image連携)が完了したはずの時点でこのG'MICブロックだけ取りこぼされていたと見られる(Fillツール同様の「後から追加され翻訳されなかった」パターン)。(2) saveToEagle()のfire-and-forget呼び出し3箇所(image-tab.jsのG'MIC自動保存、nanobanana.js、14-integrations.jsのG'MIC自動保存)は戻り値を見ていないため、Eagle保存失敗時にユーザーへは何も通知されない(今回{ok,message}を返すようにしたので、通知したければ各呼び出し元で.okをチェックするだけで対応可能)。

How to apply: 今後py/ccc.pyに新しいユーザー向けエラーを追加する際は、raise CCCError('snake_case_code', '日本語メッセージ', param1=val1, ...)のパターンに従い、i18n.jsのja/en/zh 3箇所に'err.snakeCaseCodeのcamelCase版'キーを追加すること(Node vmでの機械検証スクリプトを再利用すれば取りこぼしを防げる)。フロント側で新たにエラーレスポンスを表示する箇所を書く際は、常にresolveBackendError(data.error_code, data.error_params) || data.message || 既存の翻訳済みフォールバックの順でチェーンする。「到達可能かどうか」の判断(デッドコードか、実際に画面に表示されるか)を最初に済ませてから着手すると、翻訳すべき対象を無駄なく絞り込める。


2026-07-15(機能追加4件 + i18n Phase 3完全完了: ヘルプ訳抜け修正・付録4件翻訳)

前回セッション(7/14 I2I連携)以降のユーザー要望に対応する形で、Imageタブ・レイアウトタブ・Nanobananaタブに小粒の機能を4件追加し、その後i18n(多言語化)の残作業を確認して着手した。

機能追加(詳細は各コード参照、ヘルプ22-help-tab.jsのja版は都度更新済み):

  • Imageタブ Drawツール: カラーピッカー右隣にスポイトボタンを追加(image-tab.js)。押下でカーソルがスポイト形状(インラインSVGのカスタムカーソル)になり、キャンバスクリックで合成表示キャンバス(ie-canvas-draw)からgetImageDataで色を取得しブラシカラーに設定。クリック後・Escキーで自動的にOFFに戻る。
  • レイアウトタブ: 選択中オブジェクト(画像・フキダシ・テキスト・図形・グループ)をDelete/Backspaceキーで削除できるように(05-groups-move.jsにdeleteSelectedObject()/initLayoutDeleteShortcut()新設、01-state.jsから呼び出し)。duplicateSelectedObject()と同じ優先順位判定ロジックを流用し、_isObjectLocked()でロック中は無効化。
  • Imageタブ Shapeツール: Same Layerトグル(オプションバー左端、Shapeドロップダウンの左隣、デフォルトON)を追加。ONだと同一シェイプレイヤーへの重ね描きを継続し、OFF(または別レイヤーに切替後)だと従来通り描画のたびに新規レイヤーを作成(image-tab.js)。
  • Nanobananaタブ: 生成画像の保存先を、プラグイン内output/nanobananaからComfyUI本体のoutputフォルダ配下cc_nanobanana(folder_paths.get_output_directory()基準)に変更(py/config.pyのOUTPUT_NANOBANANA_DIRのみ変更、ルート・保存処理は無改修)。既存生成画像8件を新フォルダへ移動済み。設定はComfyUI再起動後に反映(ユーザーがStabilityMatrix経由で再起動)。
  • 不要フォルダの削除: 上記変更で不要になったcomfyui-comic-creater/output/nanobanana(空)、コード上どこからも参照されていなかったcomfyui-comic-creater/output/comfyui(空)・comfyui-comic-creater/data/(workflows/flux2Klein9BImageEdit_v10.json等、未参照)をユーザー確認の上で削除。

i18n Phase 3の「訳抜け」発覚と修正: 上記4機能のヘルプ追記後、ユーザーから「i18n化作業の残りを確認して順に作業したい」と依頼があり調査したところ、Phase 3(2026-07-12、ヘルプタブ主要12セクション翻訳)のアーキテクチャ上の弱点が判明した。_getHelpData()は_HELP_I18N[lang][id]が存在すればセクション配列をまるごと差し替える設計のため、Phase 3完了後に日本語版(_HELP_DATA)へ追記された内容はEN/ZH側に自動反映されない。具体的には、Fillツールの説明(2026-07-13追加)がimage-tabのEN/ZHから丸ごと欠落しており、翻訳者がPhase 3当時取りこぼしていたことが判明。加えて今回追加した4機能のヘルプ文言もEN/ZH未反映だった。layout(Delete/Backspaceキー)・nanobanana(保存先)・image-tab(スポイト・Same Layer・Fillツール一式)のEN/ZHセクションを修正し、Kaptureで英語・中国語UIに実際に切り替えて表示を確認した。

i18n Phase 3残タスクの完了: Phase 3で意図的に対象外としていた付録4セクション(appendix-svg-template/appendix-balloon/appendix-inkscape-template/appendix-inkscape-balloon、計約3,300文字、Inkscapeでのテンプレート/フキダシ作成手順を含む)をEN/ZHに翻訳し追加。これでヘルプタブ全16セクションがja/en/zh完全一致となり、Phase 3が完全に完了。機械検証(Node vmでファイルを評価し、_HELP_DATAと_HELP_I18N.en/.zhのid集合・セクション数を突合)でja/en/zh間の不一致ゼロを確認。

残タスク: Phase 4(py/ccc.pyのG'MIC/Eagle関連バックエンドエラーメッセージの多言語化。フロントのt()とは別の仕組みが必要、未着手)。

How to apply: _HELP_I18N方式(セクション単位で丸ごと差し替え)を使う限り、日本語版_HELP_DATAに既存セクションへの追記・修正を行った際は、同じセッション内で_HELP_I18N.en/.zhの対応セクションも必ず更新すること(今回のような訳抜けの再発防止)。機械検証スクリプト(Node vmで_HELP_DATAと_HELP_I18Nのid集合・セクション数を突合)はこの用途に再利用できるので、ヘルプ文言を触った際は都度実行するとよい。フォルダ削除等の破壊的操作は、削除前に必ず参照有無をgrepで確認し、既存データがあれば移動要否をユーザーに確認してから実施すること。

How to apply(機能面): Imageタブでピクセル色を扱う新機能を追加する際は、常に表示用の合成キャンバス#ie-canvas-drawが最新の合成結果を保持している前提でgetImageDataすればよい(DrawTool自身のオフスクリーンキャンバスとは別物)。レイアウトタブでキーボードショートカットを新設する際は、_isObjectLocked(el)(03-layers-panel.js)とduplicateSelectedObject()の優先順位判定パターン(state.selectedGroupId → selectedShapeId → selectedImageEl → selectedTextEl → selectedDrawId)が既存の参考実装として再利用できる。


2026-07-14(レイアウト/Image: I2I連携機能を新規実装 — Workflow Studio経由)

SPA化時に削除済みだった旧web版(eagle_comic_creater_web)のI2I機能(ComfyUI直結の画像生成)を、Workflow Studio(ComfyUI-Workflow-Studioカスタムノード)とのiframe連携という新設計で復活させた。ユーザーは選択肢のうち「半自動連携」(Comic Creater側は画像送信のみ担当し、ワークフロー選択・生成実行はWorkflow Studio側で行う)を選んだ。

アーキテクチャ: comic-creater側のpy/ccc.pyにはComfyUIのプロンプトキューを叩くAPIが無いため、Workflow Studio(同一ComfyUIプロセス・同一オリジンで動作、/wfm)とのiframe連携を採用。既存の双方向連携パターン(Comic Creater→WSはGalleryをiframe埋め込み済み14-integrations.js、WS→Comic CreaterはGalleryタブの「Send CC」ボタンがwindow.parent.insertImageFromUrl()を直接呼ぶ実績)を踏襲・拡張した。

  • 送信(Comic Creater→WS): レイアウトタブ・Imageタブに「I2Iへ送る」ボタンを新設。画像をBlob化→iframe.contentWindow._wfmReceiveImageForI2I(blob, name)(WS側gallery-tab.jsに新規追加)を呼ぶ→WSの「workflow studio」タブへ自動切替+Generate UI Input/Imageタブへ自動切替+画像スロットへ自動セット。共通送信関数sendImageToWorkflowStudioI2I(14-integrations.js)を新設し、レイアウトタブ(15-pixifx-bridge.js sendSelectedImageToI2I)・Imageタブ(image-tab.js _sendToI2I)の両方から呼ぶ。
  • 受信(WS→Comic Creater): 既存のSend CCボタンを拡張。window._ccI2ITargetMode('layout'|'image'、送信元が記録)を見て、レイアウトタブへの挿入(insertImageFromUrl)かImageタブへの読み込み(window._ccImageTab.loadFromUrl)かを分岐。
  • プレビューのURLキャッシュ問題: 同一ファイル名で続けて送信すると、ComfyUIサーバー上では正しく上書きされるのに、WS側のプレビュー<img src="/view?filename=...">がURL文字列不変のためブラウザキャッシュで古い画像を表示し続ける不具合があった(「合成結果(shape/draw等)が反映されていないように見えるが、実際に生成すると反映されている」という診断が難しい症状として発現)。送信のたびにファイル名へミリ秒精度のタイムスタンプを付与してユニーク化し解決(WS側は無改修)。
  • デフォルトI2Iワークフロー機能: 設定タブに「I2I設定」ブロック(チェックボックス+ワークフローファイル名、localStorage: ccc_i2i_settings)を新設。有効時、送信前にWS独自API GET /api/wfm/workflows/raw?filename=X(対象はComfyUI_5/user/default/workflows/、WS自身も同じ仕組みで自身の「起動時デフォルトワークフロー自動読込」機能を持つ)でワークフローJSONを取得し、_wfmReceiveImageForI2Iの第3・第4引数として渡す。WS側はloadWorkflowIntoEditor(workflowData, filename)(generate-tab.js既存export)を画像スロットセット前に呼ぶだけで、これがcomfyUI.currentAnalysisの再解析・UI再描画まで行うため、直後のapplyImageToSlotが新しいワークフローのLoadImageノードを正しく参照できる。
  • モデルリスト未取得バグ: iframe初回ロード直後など、WS内部のcomfyEditor.models(Checkpoint/VAE等の一覧、init()時に1回だけ非同期取得)がまだ空のタイミングでloadWorkflowIntoEditorを呼ぶと、Model/生成UIタブのドロップダウンが空のままレンダリングされ、ドロップダウンでの変更もできなくなる不具合があった(ユーザー報告「ComfyUI再起動しても直らない」)。_wfmReceiveImageForI2I内でワークフローロード前にcomfyUI.checkConnection()→(comfyEditor.models.checkpointsが空なら)comfyEditor.loadModelLists()を保証するよう修正。

教訓(プロジェクト構成・要注意): Workflow Studioのコードは実行環境(ComfyUI_5\custom_nodes\comfyui-workflow-studio)と開発元ソース(comfyUI-wf-maneger\ComfyUI-Workflow-Studio)が別ディレクトリで存在し自動同期されない。WS側のファイル(gallery-tab.js)を編集する際は必ず両方に同じ変更を適用すること(PixiJS連携ノードのweb/particle_engine.jsと同様の運用、上記「PixiJS FX連携」節参照)。今回一度この二重更新をユーザーに指摘されて気づいた。

教訓(Kapture経由のiframe内操作は座標クリックに頼るしかなく不安定): KaptureのelementsツールやCSSセレクタ指定のclickはトップレベルdocumentのみが対象で、埋め込みiframe内部(別オリジンでなくても)の要素には到達できない。座標(x,y)クリックはiframe内にも転送されるため唯一の操作手段になるが、スクリーンショットからの目視座標読み取りは誤差が大きく、タブ切り替えのような大きなボタンは数回の試行で成功する一方、密集したヘッダーボタン行は何十回試しても安定してヒットしないことがあった。iframe内操作を伴う機能の自動検証はコストが高いため、コードの静的な正しさを確認できた時点で見切りをつけ、実機での最終確認はユーザーに依頼するのが現実的。

ヘルプ(22-help-tab.js)のレイアウト/Image/設定タブに、I2I連携・I2I設定の説明を3言語(日本語・英語・中国語)で追加。

Why: ユーザー要望「レイアウトタブ、ImageタブにI2I機能を追加したい。SPA化前のEagle Comic Createrにあった機能」。ComfyUIタブ削除に伴いWorkflow Studioとの連携が必要という制約があり、事前調査(旧web版のI2I実装・現SPA版の既存連携パターン)を経て半自動連携方式で合意した。

How to apply: I2I関連の追加修正時は、Comic Creater側(static/js/main/14-integrations.jsのsendImageToWorkflowStudioI2I/image-tab.jsの_sendToI2I/15-pixifx-bridge.jsのsendSelectedImageToI2I)とWorkflow Studio側(gallery-tab.jsのwindow._wfmReceiveImageForI2I、実行環境・開発元の両方)を意識すること。デフォルトワークフローの読み込み元はComfyUI_5/user/default/workflows/固定(WS独自API経由)。


2026-07-13(レイアウト: ドロー/画像/フキダシの回転ハンドル・選択枠のズレを修正)

ユーザー報告「ドローツールのフレームシェイプとずれる回転ハンドルが逆方向に動く」。実機再現すると、回転させた図形に対して選択枠(点線のバウンディングボックス)と実際の図形の見た目がだんだんズレていき、回転ハンドルの表示位置も実際の回転方向と逆にズレていく問題だった。

原因(3つ、17c-layer-draw-handles.jsのドローツールで発見):

  1. 回転ハンドルの位置計算式でMath.sin(rad)の符号が逆: 図形本体は標準の回転行列(SVGのtransform="rotate(angle,cx,cy)"、x'=cx+lx·cos−ly·sin, y'=cy+lx·sin+ly·cos)で回転するのに対し、renderDrawShapeHandles/updateDrawShapeHandles内のハンドル位置計算はこの行列から逸脱した符号になっていた
  2. 選択枠(bbox)がtransformを持たず常に無回転のまま描画されていた(図形本体だけが回転し、点線の選択枠は元の位置に取り残される)
  3. 8点リサイズハンドルも回転を考慮せず軸並行位置に配置されていたため、回転時に選択枠の角とハンドルの丸がズレる

修正: 回転を考慮した8点ハンドル位置を計算する_drawShapeGetRotatedHandlePositions、回転ハンドル位置を計算する_drawShapeGetRotateHandlePosを新設し、bbox(rotate(angle,cx,cy)のtransformを付与)・8点ハンドル・回転ハンドルの3つを統一的に回転行列で配置。リサイズドラッグ処理(initDrawShapeManipulation)も、マウス移動量を図形のローカル座標系へ逆回転してからリサイズ計算し、回転中心のズレをグローバル座標系に変換して補正するロジックに変更(回転していない図形と同じ操作感になるように)。

ユーザー確認の上、同じ3点のバグが画像(08-panels-images.js、SVG画像挿入も同じinserted-image実装を共有するため同時に解消)・フキダシ(09c-balloon-handles.js/09d-balloon-tools.js)にも存在すると判明し、同じパターンで修正。フキダシはさらに「尻尾ハンドル」「カーブハンドル」も無回転のローカル座標のまま計算されており、回転したフキダシで尻尾の操作が混乱するとの追加報告を受けて対応:_h2CalcCurveHandlePosをローカル座標系で計算した後にフキダシの回転角を適用して絶対座標に変換する方式に変更し、尻尾ドラッグのマウス角度計算もローカル座標系への逆回転を挟むように修正。

検証: Kaptureで実際に-13.3度/26.3度/-47.4度回転させた図形・画像・フキダシに対し、DOM属性を数値検証。bboxのtransformが図形本体と完全一致、8点ハンドルの対角ペア(nw⇔se、ne⇔sw)の中点が理論値と一致、回転ハンドル位置も理論式と一致することを確認。フキダシの尻尾ハンドルは、パスのローカル座標をPythonで実際に回転行列変換した値とハンドルの実測値が小数点以下15桁まで完全一致した。

ヘルプ(22-help-tab.js)は既存の「黄色ハンドルで回転できます」等の説明がそのまま正しい記述のため変更なし。

How to apply: SVGのtransform="rotate(angle,cx,cy)"で回転する要素に対し、別要素として描画する選択枠・ハンドル類を実装する際は、必ず同じ回転行列(x'=cx+lx·cos−ly·sin, y'=cy+lx·sin+ly·cos)で位置計算するか、選択枠自体に同じtransformを付与すること。ドラッグ操作(リサイズ・尻尾等の角度計算)でグローバル座標のマウス位置を扱う場合も、回転している要素のローカル座標系に逆回転してから計算しないと、回転量に応じて操作感がズレていく。


2026-07-13(Imageタブ: バケツ(Fill)ツールを新規実装 — 単色/グラデーション塗り)

Imageタブに塗りつぶし機能が無かったため新規追加。単色フラッドフィルに加え、線形・円形グラデーション塗りにも対応(comfyUI-particle-pixijsのパーティクルカラーランプUIを参考にした複数カラーストップ編集・方向/強さのミニコンパスUI)。ユーザー選択により、グラデーションはバケツツールの塗りモード(Solid/Gradient切替)として統合し、独立ツールにはしていない。

  • 新規ファイル image-tab/FillTool.js: スキャンライン式のスタックベースflood fill、evalGradient(particle_engine.jsのロジックを移植、pos昇順ソート→区間線形補間)、ストップ追加/削除ヘルパー
  • image-tab.js統合: TOOL_DEFSにfill追加(ショートカットG)、ツールオプションパネルに単色/グラデーション切替・カラーランプ(横長canvas、ストップのドラッグ編集)・方向パッド(linear=角度+強さ、radial=半径)を実装
  • カラーランプUIで踏んだ不具合2件:
    • 幅が変わる: canvas幅をclientWidthに動的同期する実装が原因。canvas要素はreplaced element特有の挙動でwidth属性変更がflexレイアウトの基準サイズにも影響し、再測定のたびに幅がズレて縮んでいった。固定値(130px、パネル内側幅約148pxに収まる安全マージン)に戻して解消
    • ハンドルが消える: ストップをmousedownで選択した際に_renderFillProps()(DOM全体再構築)を呼んでおり、<canvas id="ie-fill-ramp">要素ごと新しいものに置き換わっていた。ドラッグ中のmousemove/mouseupハンドラはDOMから切り離された古いcanvas変数を参照し続けるためgetBoundingClientRect()が0を返し、座標計算がNaN化してハンドルが描画されなくなっていた。DOM全体再構築をやめ、ランプの再描画と色input欄の値更新だけの部分更新に変更して解消
  • ヘルプ(22-help-tab.js)のImageタブ「ツール」セクションにFillの説明を追加

検証: Kaptureで実機確認。単色フラッドフィル・線形/円形グラデーション塗り・ストップ追加/削除・Undo・幅固定後の色変更でcanvas幅が変わらないことを確認。

How to apply: テキスト/Shapeツールの塗りへのグラデーション適用は今回のスコープ外(将来課題)。FillTool.evalGradientとランプUIの実装パターンはツール非依存にしてあるので、将来グラデーション塗りを他ツールに広げる際に再利用できる。またSelectツールへの矩形/円形「領域選択」も別途要望あり(未着手)だが、floodFillのマスク生成と_fillMaskWithGradient(mask, bbox)の塗り適用を分離しておいたので、選択マスクができれば同じ塗り適用ロジックを再利用できる設計にしてある。


2026-07-13(レイアウト/Image: 図形・フキダシ・SVGのPNG変換を「元の位置・サイズを保った複製挿入」に統一)

一連のユーザー要望に対応。要点は「PNG変換系の機能はすべて、コマ幅いっぱいに拡大するのではなく、元のオブジェクトと同じ位置・表示サイズで複製挿入する」という仕様に統一したこと。

  • insertImage/insertImageToOverlayにplacement引数を追加(08-panels-images.js): {x, y, width, height}を渡すと、従来のデフォルト挙動(コマ幅いっぱいに拡大 / ページ幅40%で中央配置)の代わりにその位置・サイズで挿入する。省略時は完全に従来通りなので既存呼び出し元(3Dポーズ確定等)は無改修。
  • ドロータブに「図形をPNG変換」ボタンを新設(17b-layer-draw-commit.js): 選択中のdraw-shape(矩形/楕円/線/曲線/多角形/鎖/ロープ/My曲線)を、元図形を残したままPNG化して複製挿入。
  • 「SVG→PNG」ボタン(18-svg-color-png.js)も同様に、変換元SVG画像要素のx/y/width/heightをそのままplacementとして渡すよう変更。従来はコマ幅いっぱいに拡大されていた。
  • フキダシ・図形共通のconvertShapeToImage(el, svgEl)を複製方式に刷新(09c-balloon-handles.js)。フキダシメニュー(「挿入」ボタン右隣)に専用の「画像に変換」ボタンを新設し、レイヤーパネルの🖼ボタン(フキダシ・図形どちらも)は撤去。

ハマった点1(座標変換): el.getCTM()は「最も近いviewport祖先のviewport座標系」への変換を返す仕様で、これはSVGのuser space座標系(viewBox適用前の座標系)ではなく、viewBoxによるスケーリングまで含んでしまう。複製先の単独SVG(親のviewBoxを持たない)にこの変換を適用すると、座標系のスケールが数十倍〜数百倍ズレて図形が描画範囲外に飛び出し、生成されるPNGが完全に透明になるという分かりにくい形で症状が出た。対策は、el.transform.baseVal.consolidate().matrixで要素自身のtransform属性のみを反映する方式に変更(回転rotate(angle,cx,cy)やpath/g用のmatrix(...)はそのまま使えるが、祖先のviewBoxスケーリングは含まれない)。 ハマった点2(Canvasサイズ上限): レイアウトのSVG座標系はページ全体でviewBox="0 0 21000 29700"という大きな数値系のため、図形のバウンディングボックスをそのままCanvas幅/高さに使うとブラウザのCanvasサイズ上限を超え、これも透明画像として出力される。出力解像度にMAX_DIM=2000の上限を設けて縮小することで解決(挿入時はinsertImageがplacementの表示サイズに再フィットさせるため画質への実害はない)。 ハマった点3(同名関数の重複定義・今回最大の詰まりどころ): convertShapeToImageをフキダシ用に新規実装した際、09b-balloon-shapes.jsの末尾に「実装コメントのみ残って本体が無い」箇所を見つけたため「分割時に本体が失われた」と判断し実装を追加した。しかし実際には09c-balloon-handles.jsに別の実装(旧仕様・要素を削除して画像に置き換える方式、i18n化済みで正常に動作する状態)が既に存在しており、スクリプト読み込み順(09b→09c)でJSの関数宣言は後勝ちのため、常に09c側の実装だけが有効になっていた。ブラウザの複数回リロード・アプリ再起動でも再現し続けたため、「サーバーが配信するファイル内容」と「ブラウザが実際に実行している関数の中身」をconvertShapeToImage.toString()で突き合わせて初めて発覚。教訓: 関数が「実装が無い」ように見えても、同名の別定義が他ファイルに無いかgrep -rn "function <name>"で必ず確認してから実装を追加すること(コメントだけ残っているのは分割時の実装移動の痕跡である可能性がある)。最終的に09b側の重複定義を削除し、09c側を新方式(複製・元要素は残す)に書き換えて解消。

検証はKaptureのevaluateでテスト用のSVG/図形/フキダシをDOM注入し、変換結果の位置・サイズ・非透明ピクセル比率を直接確認、undo()でテストデータを都度ロールバックする方式で実施(実データを汚さないまま繰り返し検証)。作業中Kapture側のevaluateが一時的に切断される場面があり、その間はファイルシステム上のコードとcurlによるサーバー配信内容の突き合わせで代替した。

ヘルプ(22-help-tab.js、レイアウトタブの「フキダシ」「形状描画(ドロー)」セクション)を3言語で更新し、上記の新ボタン・モード構成を反映。ドローサブタブの編集モードタブ名も「ボックス」→「ドロー」に変更(i18n layout.editModeBox、3言語)。

How to apply: 今後PNG変換系の機能を増やす場合はinsertImage(base64, w, h, extraAttrs, placement)のplacement引数をそのまま使えばよい。SVG要素のtransform関連を扱う新機能では、getCTM()ではなくel.transform.baseValから要素自身の変換行列のみを取り出す方式を基本にすること(祖先のviewBoxやCSSズームを巻き込まないため)。またグローバル関数を新設する際は、必ず対象の関数名で全体grepしてから着手する。


2026-07-12(i18n Phase 3: ヘルプタブの主要12セクションを翻訳)

22-help-tab.jsの_HELP_DATA長文プローズ(16セクション・約15,800文字)のうち、ユーザー選定の主要12セクション(about/page-template/layout/nanobanana/font/page-export/page-work/project/settings/image-tab/assetpanel/wfmgallery)を英語・中国語に翻訳。付録4件(SVG仕様・Inkscape手順、計約3,300文字)は使用頻度が低いため今回は対象外(ja自動フォールバックのため未着手でも問題なし)。810キー×3言語(i18n.js側はhelp.appendixDivider/help.jumpToTabの2件のみ追加、716→810は主にこのヘルプ翻訳データ自体のボリューム)。

  • アーキテクチャ: 他のPhase 2までと違い、この長文プローズはi18n.jsのフラットなt()キーには馴染まない(見出し・本文のペアが複数ネストする構造)ため、22-help-tab.js内に専用の_HELP_I18N = { en: {...}, zh: {...} }(id→{label, sections})を新設。_getHelpData()ヘルパーが現在言語のデータを_HELP_DATA(ja基準)とマージし、翻訳が無いid(付録4件)は自動的にja版にフォールバックする。_HELP_DATAを直接参照していた4箇所(_helpSorted/_helpFilterNav×2/_helpShowItem)を_getHelpData()に置き換え済み。kana(ふりがな検索用)は翻訳対象外の既存方針を踏襲し、ja版のまま全言語で共用(検索の副次的な補助のため実害なし)。
  • 固定UI文字列(付録区切り「付録」、詳細画面の「タブを開く →」ボタン)は通常のt()キー化で対応。
  • 作業方法: 巨大な1回のEdit挿入だと出力トークン制限に達するため、セクション単位(ときに2〜3件まとめて)でen側→zh側の順に小分けのEdit呼び出しを繰り返し、都度node --checkで構文確認する方式で進めた。最後にvmモジュールでファイルを安全に評価し、ja/en/zhの id集合一致・各セクション数の一致を機械的に検証(12件×2言語、不一致ゼロ)。

How to apply: 付録4セクション(appendix-svg-template/appendix-balloon/appendix-inkscape-template/appendix-inkscape-balloon)を翻訳する際は、同じ_HELP_I18N.en/.zhにidキーで追記するだけでよい(_getHelpData()のフォールバック判定は「idがtr内に存在するか」なので、追加すれば自動的に有効になる)。長文コード例(<pre class="help-code">)はHTMLごとそのまま流用し、コメント部分(&lt;!-- ... --&gt;)のみ翻訳すること。


2026-07-12(i18n Phase 2⑥: 3Dポーズ/G'MIC・Eagle連携/残りファイル全対応でPhase 2完全完了)

Phase 2ロードマップの最終区分(3Dポーズ23/G'MIC・Eagle連携14/その他00-03,05,12-14,16,18)に対応し、Phase 2を完全に完了。808キー×3言語(前回716→+92キー)。対象11ファイル: 00-db.js/01-state.js/02-assets.js/03-layers-panel.js/05-groups-move.js/12-text-png-export.js/13-export-pdf-epub.js/14-integrations.js/16-processing-edit-tabs.js/18-svg-color-png.js/23-pose3d-bridge.js。t変数のシャドウは事前grepで全ファイルなしを確認済み。

  • 重複キー混入を発見・修正: 前回セッション(Phase 2⑤スクリプトタブ)で新設したscript.noSavedWorks/script.noPageWorksが、実は既存のasset.noSavedWorks/asset.noPageWorks(Phase 1でアセットパネル「S」タブ用に作成済み)と文言が完全一致する重複だった。21-script-tab.js側の参照をasset.*に差し替え、script.*の重複キーを3言語×2キー削除。教訓: 新規キー追加前に同名・同義の既存キーがないか名前空間を跨いでgrepする習慣が必要(今回はasset.*パネルの空状態メッセージという別機能のキーだったため見落とした)
  • image-tab.jsとの表現統一: G'MIC関連の一部メッセージ(画像をサーバーへ送信中.../G'MIC GUIがキャンセルされました/G'MIC GUIで編集中...)はImageタブ側(Phase 2④で作成済みのimage.gmic*)と文言が完全一致したためそのまま再利用。レイアウトタブ独自の文言(自動保存版の編集中ヒント等)のみlayout.gmic*として新設
  • 軽微な副次修正: 14-integrations.jsのgmicWaitForJob内e.message.includes('タイムアウト')という日本語部分文字列チェックが、実際にはtry/catchの外で投げられるタイムアウトエラーには到達しない元から機能していないデッドコードだったと判明。翻訳に合わせてe.message === t('layout.gmicTimeoutError')への置き換えついでに気づいたが、元々無害な死んだ分岐のため動作への影響はない
  • image-tab/*.js系(LayerManager/DrawTool/TextTool/SelectTool/ShapeTool/MaskTool/MaskEditorOneTools)は対象ゼロを確認済み(Phase 2④で判明、再確認不要)

検証方法: 前回までと同様、node --check+全.jsファイル横断でのt()呼び出しキー全数照合(438種類、ja辞書への存在確認)+ja/en/zh 3言語のキー集合完全一致チェック。ユーザーが使用量節約のため実機確認を担当する方針のため、Kaptureでの目視確認はPhase 2全体を通じて省略。

How to apply: 次はPhase 3(22-help-tab.jsの_HELP_DATA長文プローズ翻訳)またはPhase 4(py/ccc.pyのG'MIC/Eagleエラーメッセージ、バックエンドのためフロントt()とは別の仕組みが必要)。新規キー追加時は必ずgrepで同名・同義キーの重複を確認すること(今回の教訓)。


2026-07-12(i18n Phase 2⑤: スクリプトタブを翻訳)

main/21-script-tab.jsに対応。716キー×3言語(前回697→+19キー)。プロットテーブルの見出し(シーン/コマ番/セリフ番/セリフ・説明等)、保存済み作品一覧の空メッセージ、セリフ行・ページ・作品の追加/削除/保存/読込に伴うalert/confirm文言をscript.*名前空間に追加。「要素」列見出しは既存のscript.subtabElements(Phase1でサブタブラベルとして追加済み)をそのまま再利用。tのシャドウは無し。前回同様、node --check+t()キー全数照合+3言語キー集合一致チェックのみで検証(実機確認はユーザー側)。

How to apply: 次回はPhase 2⑥(3Dポーズ23/G'MIC・Eagle連携14/その他00-03,05,12-14,16,18)。14は今回layout.msgSelectImageFirst等いくつかのキーを先取りで用意済みなので、着手時に14側の同一文言をそのキーに差し替えること。


2026-07-12(i18n Phase 2④: Image連携/Nanobanana/PixiFX を翻訳)

多言語化Phase 2の④(image-tab.js・image-tab/*.js・nanobanana.js・15-pixifx-bridge.js・text-style-modal.js)に対応。697キー×3言語(前回630→+67キー)。ユーザーが使用量節約のため実機検証を担当する方針だったので、node --check+全ファイル横断でのt()呼び出しキー全数照合(ja辞書に対する存在チェック)+ja/en/zh 3言語のキー集合完全一致チェックのみで完了とした(Kaptureでの目視確認は省略)。

  • image-tab/*.js(LayerManager/DrawTool/TextTool/SelectTool/ShapeTool/MaskTool/MaskEditorOneTools)は対象ゼロ: 事前調査で日本語を含む行を洗い出したが、全てコード内コメント(this.x = 0; // 左上X等)で、ユーザー向け文字列は皆無だった。翻訳不要と判断し変更なし
  • image-tab.js: 調整レイヤー12種のラベルは既存image.adj*キー(HTML側Phase1で既存)をそのまま再利用。新規追加は約35キー(G'MIC進捗文言、Close確認ダイアログ、レイヤー複製・統合のtoast/confirm、Upscaleのstatus文言、Textツールのスタイルボタン等)
    • ハマった点: Shapeツールの_renderToolOptions分岐内でconst t = this._shapeTool;と、i18nのt()関数をシャドウする変数が既に存在していた。この中の4箇所(間隔ラベル・mychain選択option・画像選択ボタン・未選択フォールバック・mychainロード失敗toast)はwindow.t(...)で明示参照する必要があった(見逃すとt.spacingのような呼び出しと衝突してSyntaxErrorではなく誤動作するため要注意)
  • text-style-modal.js: フォント管理タブの「スタイル」タブ(Phase 2③で既に翻訳済み)とほぼ同一内容のモーダルのため、新規キーはタイトル・スタイル名検証・削除確認の4個のみで、残りはfont.*/common.*の既存キーを全面再利用
  • nanobanana.js: 接続ステータス3種・I2I枚数上限アラート(関数値キーで枚数を補間)・生成フロー中のstatus文言をnb.*に追加
  • 15-pixifx-bridge.js: PixiFXモジュール未読込・画像未選択のアラート文言を追加。「画像を選択してください。\nレイアウトタブで...」は14-integrations.jsのG'MIC連携でも同一文言が使われているためlayout.msgSelectImageFirstとして汎用キー化した(14はPhase 2⑥で未着手のため、着手時にこのキーを再利用すること)
  • 新設の汎用キー: common.close/common.apply/common.done/common.errorPrefix((m) => \エラー: ${m}``形式、"エラー: "+message パターンが複数ファイルに散在していたため統一)

How to apply: 次回はPhase 2⑤スクリプトタブ(21-script-tab.js)から。Shapeツールのような「ローカル変数tによるシャドウ」は他のタブでも起こりうるパターンなので、置換前に対象スコープでtという名前の変数・パラメータが使われていないか確認すること。


2026-07-12(レイアウトタブ ドローツール: 線幅初期値の変更+鎖/ロープのプレビューと確定の太さ統一)

ユーザー要望2件。

1. 形状選択時の線幅初期値(17a-layer-draw-input.js の形状change時): 直線・曲線・多角形を5→50に変更(鎖・ロープ=80、My曲線=5は従来通り)。多角形は従来初期値の自動設定自体が無かったため分岐を追加(線幅のみ設定、塗り/線の有無は維持)。

2. 鎖・ロープの「描画時の太さ」と「確定後の太さ」のズレを修正。プレビュー(17a)と確定(17b)の数式自体は等価だったが、ズレの真因は確定直後のUI同期: 確定後に_layerDrawSelectShape→_drawShapeSyncProps(17c)が走り、鎖/ロープの<g>はstroke属性を持たないため「線なし・線幅0」をUIへ書き戻していた。次に描画するとプレビューは0→フォールバック5で細く描かれ、確定は線なし→0扱いで解釈が割れる。修正内容(17b-layer-draw-commit.js):

  • 鎖/ロープの確定時の太さ・色を、プレビューと同一の解釈(parseFloat(線幅)||5、色は線カラーピッカー値を線なしチェックに関わらず使用)で計算するcellW/cellColorに統一
  • 確定した<g>自体にstroke/stroke-width(論理値)とfill="none"を付与し、UI同期が正しい値を読み戻せるようにした。セルは個別のstroke-widthを持つため見た目には影響しない。ロープのベース矩形(rect1)だけstroke属性が無く親から継承してしまうため、明示的にstroke="none"を追加(これを忘れると全セルに枠線が付く)

Kaptureで形状ボタン切替時の初期値(直線50/曲線50/多角形50/鎖80)を実機確認済み。ドラッグ描画はKaptureで再現不可のため、鎖/ロープの太さ統一はコードパスの検証(プレビューと確定の式の同一化)による。


2026-07-12(ページタブ: テンプレートカードにサイズとコマ枠幅を表示)

ユーザー要望。テンプレートサブタブの各カードに、テンプレート名の下(グループ名の上)に「サイズ / 枠幅」(例: 21000×29700 / 枠幅 63)を表示するようにした。

  • サイズは保存済みのtemplate.width/height(viewBox由来、表示は整数丸め)
  • コマ枠幅は保存データに無いため、_tmplGetFrameWidth()(06c-template-wizard.js)でsvgContentの2番目のpolygon(1番目=panel_0はページベースのため除外)のstroke-width属性/styleから抽出。取得できない場合(外部SVGでCSSクラス指定等)はサイズのみ表示
  • i18nキーtmpl.cardInfo(関数値、fw=nullで枠幅省略)を3言語追加

2026-07-12(Imageタブ: Textツールでも既存テキストをダブルクリックで再編集できるように)

ユーザー要望「Textツール時の編集オーバーレイを開く操作を、Selectツールと同じダブルクリックにしたい」。従来はTextツールでシングルクリックすると位置を問わず新規テキスト入力が開き、既存テキストの上でも「再編集」ではなく新規入力になっていた(再編集はSelectツールに切り替えてダブルクリックする必要があった)。

  • image-tab.jsに_findTextLayerAt(x, y)を新設(最前面から可視テキストレイヤーをヒットテスト、SelectTool._isPointInLayerを再利用)
  • Textツールのmousedown: 既存テキストレイヤー上ではシングルクリックで新規入力を開かない(入力オーバーレイ表示中は従来通りクリックで閉じる挙動を維持)
  • _onOverlayDblClickをTextツール対応に拡張: 座標上のテキストレイヤーをアクティブ化して_openTextEditForLayer(Selectツールの再編集と同じ経路)で開く
  • ハマりポイント: dblclickリスナーは#ie-canvas-overlayにしか無かったが、overlayはpointer-events:none(SelectTool.activate()時のみauto)のため、Textツール時のイベントは#ie-canvas-drawに落ちる。drawCanvas側にもdblclickリスナーを追加して解決
  • 検証時の注意(キャッシュ): /ccc_staticはCache-Control未設定のためブラウザのヒューリスティックキャッシュで古いJSが通常リロードでも使われることがある。検証時はJSのURLを直接開いてリロード→戻る、またはハード再読み込み(Ctrl+F5)が必要だった

Kaptureで検証済み: Textツールで既存テキスト上のシングルクリックが抑止されること、空白クリックで従来通り新規入力が開くこと(ダブルクリック発火はKapture非対応のため、Selectツールで実証済みの_openTextEditForLayer経路とイベント配線の確認をもって代替)。


2026-07-12(テキストスタイルv2: フォントサイズ相対値化+3タブの描画基準統一)

ユーザー報告「フォントタブで作ったスタイルがレイアウトタブではまったく異なる見た目になる。フォントタブとImageタブの表示も違う」。調査の結果、共通のスタイルデータ(fontmgr_text_styles)を3箇所が異なる単位・異なる描画基準で解釈していたことが原因。

旧仕様の問題点:

  1. 単位: スタイル値が絶対値で、フォントタブ=プレビューpx(150px基準)、Imageタブ=Canvas px(64px基準)、レイアウトタブ=SVG単位(150pt=529単位基準)。同じ線幅4でも文字サイズ比が最大13倍違い、レイアウトでは「ほぼ効いていない」見た目になっていた
  2. 描画基準: 線=Imageタブだけ2倍太い(lineWidth=S×2)、袋文字帯=フォントタブだけ半分(B/2)、影ぼかし=レイアウトだけ2倍ぼける(feDropShadowのstdDeviationはσ直指定、CSS/Canvasのblurはσ≈blur/2)、影の対象=レイアウトは袋文字込み/他は線+塗りのみ
  3. プレビュー: スタイルモーダルはSVG(レイアウトと一致)だが、フォントタブのスタイル/プリセットプレビューはCSS2枚重ねでどちらとも不一致

新仕様(v2、ユーザー合意: 既存スタイルは破棄・既存作品内の適用済みテキストは無変更):

  • スタイル値は**「フォントサイズ100pxあたりのpx」の相対値**。適用時に対象のfont-sizeに比例スケール(k=fontSize/100)するため、どのタブ・どの文字サイズでも同じ比率になる
  • 描画基準はSVG(レイアウト)側に統一: 線=中央基準(外側S/2)、袋文字=線の外にBの帯(Canvas側はlineWidth=(stroke有効時S+B×2)×k、stroke無効時はSを含めない)、影ぼかし=SVG側をstdDeviation=(blur/2)×kに補正、影=最背面シルエット(袋文字込み)に1回だけ(Canvas側は最初に描くパスにのみshadow設定、従来の線+塗り両方に掛かって影が濃くなる問題も解消)
  • 保存データにv: 2を付与し、load時にv===2のみ読み込む(19-font-manager.jsの_fontMgrLoadStylesとtext-style-modal.jsのloadStylesの2箇所)。旧スタイルは一覧から消える
  • フォントタブのスタイル/プリセットプレビューをCSS2枚重ね→モーダルと同じSVG方式(新設_fontMgrRenderStylePreviewSvg、実適用と同じ_fontMgrApplyStyleAttrsToTextElを通す)に置き換え。index.htmlの#style-preview-back/frontdivは#style-preview-svgに差し替え。CSS版_fontMgrRenderTextStylePreviewはアセットパネルFタブのミニサムネ専用として残し、スケール補正+基準補正を適用
  • _fontMgrExtractStyleFromTextEl(モーダルを開く際の逆変換)はfont-sizeで割り戻すため、旧仕様の絶対値で作られた既存テキストも「現在の見た目」を正しく相対値として取り出せる(再編集時に見た目が変わらない)
  • レイアウトタブでフォントサイズ変更時(09aのfont-sizeハンドラ)にスタイルを抽出→再適用し、線・袋文字・影がサイズに追従するようにした(旧仕様では固定のままだった)
  • ImageタブはtextPropsに相対値が入り_rerenderTextLayerがk倍して描画(サイズ変更に自動追従)。_textExtraPadもスケール対応

変更ファイル: 09a-balloon-init.js / 09e-text-tool.js / 19-font-manager.js / 20-font-presets.js / image-tab.js / text-style-modal.js / templates/index.html

検証: Kaptureでスタイル(線4白・袋文字8黒・影4/4/4)を作成→フォントタブSVGプレビュー・レイアウトタブ挿入(font-size529でstroke-width=21.16、feMorphology radius=42.32、stdDeviation=10.58と正しくスケール)・Imageタブ挿入(同比率で描画)を確認。既存の適用済みテキスト(セイセイスルモノ等)の属性は無変更のまま。検証データは削除済み。

注意: 見た目の完全一致の唯一の例外は袋文字の角の形状(SVGのfeMorphologyは矩形カーネルで角ばる、Canvasはround join)。また旧仕様時代にImageタブの保存済みプロジェクト(.json)に入っているtextPropsの絶対値は、再編集時に相対値として再解釈される(フォントサイズ64なら約1.5倍太くなる)。


2026-07-12(レイアウトタブ: 画像によってG'MIC GUIが起動しない問題を修正)

ユーザー報告「レイアウトタブの画像ツールで画像によってG'MIC GUIが起動したりしなかったりする(同じ画像でもImageタブ経由なら起動する)」。Kaptureで実機再現し、失敗画像の送信ボディを捕捉して原因を特定した。

原因(2つの複合):

  1. Nanobanana生成画像のMIME偽装: Gemini APIは実体JPEGのbase64を返すことがあるが、nanobanana.jsが無条件にdata:image/png;base64,を付与していた。サーバー側handle_local_gmic_open_b64はMIMEを信じて.png拡張子で一時ファイルを保存する
  2. gmic_qt.exe(3.6.5スタンドアロン版)はJPEGを開けない: 拡張子を正しい.jpgにしてもJPEG入力ではエラーも出さず終了コード0で即終了する(GUIが出ない)。System.Drawing製の単純JPEGでも再現=JPEG全般が非対応。PNG入力なら正常にGUIが開く。フロント側にはジョブfailed: G'MIC GUIがキャンセルされましたとしか見えない

Imageタブで動いていたのは、送信前にlayer.canvas.toDataURL("image/png")で常に本物のPNGへ再エンコードしていたため。

修正:

  • py/ccc.py: _sniff_image_ext()(マジックバイトによる実形式判定)を追加し、handle_local_gmic_open_b64でMIMEより実バイトを優先。さらにPNG以外(JPEG/WebP/GIF/BMP)はPILでPNGに変換してからgmic-qtに渡す(変換失敗時は従来どおりそのまま渡すフェイルセーフ)。既存作品に保存済みの偽ラベル画像もこれで救済される
  • static/js/nanobanana.js: 生成画像のbase64先頭から実形式を判定して正しいMIME・拡張子で保存するように修正(/9j/=JPEG, UklGR=WebP, R0lGOD=GIF, それ以外はPNG扱い)

PILでの変換後にgmic_qtのGUIが正常に開くことを事前検証済み。ユーザー環境で修正後の動作確認済み。ccc.pyの変更はComfyUI再起動が必要。

教訓: gmic-qtへ画像ファイルを渡す機能は必ずPNGで渡すこと。外部API由来の画像はMIMEラベルを信用せずマジックバイトで実形式を確認すること。


2026-07-11(Imageタブ: レイヤー複製機能を追加)

ユーザー要望。レイヤーパネル下部のOpacityスライダーの下に「レイヤー複製」ボタン(#ie-layer-duplicate-btn)を追加。

  • LayerManager.js: duplicateLayer(id)を新設。canvas内容(drawImage)と全プロパティ(visible/opacity/blendMode/位置/サイズ/回転/反転/textProps=ディープコピー/locked/maskApply/operation/adjType/adjValue)をコピーし、名前は{元名} copy、idは新規。layers配列は先頭=最前面のため元のindexに挿入すると元の直上に来る。複製をアクティブに切り替え
  • image-tab.js: ボタンリスナーをie-flatten-btnの並びに追加。_syncActiveLayerFromCanvas()(描画途中の内容を同期)→_saveUndo()→複製→合成ビュー/レイヤーリスト更新→toast。テキスト・調整・マスクレイヤーも複製可能(textPropsコピーで複製後の再編集も機能する)
  • ボタン行は既存.ie-adj-add-rowクラスを再利用(border-top付きの行スタイル)。i18nキーimage.duplicateLayer/image.duplicateLayerTitleを3言語追加

追記: ヘルプタブを本日分の変更に合わせて更新(同日)

_HELP_DATA(22-help-tab.js)の4セクションを更新: ①設定=「言語」「G'MIC設定」(パス設定・要再起動不要)を追加 ②Image=Textツールの縦書きチェック・スタイル一本化(B/I/Align廃止)・ダブルクリック再編集でスタイル保持・レイヤー複製ボタン・FilterのG'MICパス参照先 ③レイアウト「テキスト」=縦チェックの即時反映・色プリセット化(任意色はスタイルの塗り)・☆ボタン廃止・スタイルモーダルの項目拡大とSVGプレビュー ④フォント「スタイルの作成・保存」=太字・文字寄せの追記。


2026-07-11(Imageタブ: スタイルの「下線」対応+テキスト再編集でスタイルが消えるバグを修正)

前エントリの既知の制約だった「スタイルのunderlineEnabledがImageタブで効かない」を解消(ユーザー依頼)。

  • 下線の自前描画: Canvas2Dにはtext-decorationが無いため、_rerenderTextLayerの塗りパスの後にfillRectで描画する。横書きは各行の下(上端から0.95em、太さ0.06em、行幅はmeasureText+align考慮)、縦書きは列の右側の傍線(列中心+0.5emの位置に列高さ分)。影の設定が残った状態で描くので文字と同様に影が付く。色は塗り色(text-decorationと同じ挙動)
  • TextTool.jsのlayoutVerticalText()の戻り値にcols: [{cx, h}](列ごとの中心x・高さ)を追加(傍線の描画に使用)。単体テスト(scratchpadのtest-vertical.mjs)にcols検証を追加して全通過(※既存テストの句読点アサーションが0.5em時代のまま古くて落ちたので0.3emに更新した——実装の回帰ではない)
  • _fontStyleAttrsFromStyle()にunderline、getSelectedTextStyleInfo()にunderlineEnabledを追加(スタイル⇔textPropsの双方向変換)
  • 既存バグ修正: テキスト再編集(ダブルクリック→OK)でスタイルが全部消えていた。_editingTextLayerパスがlayer.textProps = props(完全置換)+data.canvas(スタイル無し描画)だったため、線・袋文字・影・太字斜体・下線が失われていた。{ ...layer.textProps, ...props }で既存スタイル系プロパティを保持し_applyTextPropsToLayer()(スタイル余白込み再計測+中心保持+スタイル込み再描画)を通す方式に変更

追記: 下線にも線・袋文字を付ける(同日、ユーザー指摘)

モーダル(SVG)では下線もtext-decorationとしてstroke・feMorphologyフィルタの対象になり縁取りが付くが、Canvas側は塗り+影のパスでしかfillRectしていなかったため、下線だけ縁取りなしでズレていた。下線矩形を事前計算してdrawPass()に組み込み、袋文字(strokeRect太)→線(strokeRect)→塗り(fillRect)の3パスすべてで文字と一緒に描く方式に変更(strokeTextとstrokeRectでlineWidth=2倍・lineJoin=roundの扱いが同じため見た目が一貫する)。


2026-07-11(テキストスタイルに太字・文字寄せを追加、Imageタブへの斜体等の反映漏れを修正)

ユーザー要望「Imageタブのテキストで太字・斜体・文字寄せドロップダウンもスタイルに入れたい。スタイルで斜体にしても有効にならない原因と思う」。

原因(斜体が効かない): スタイルには元々italicEnabled/underlineEnabledがあるが、Imageタブへの変換_fontStyleAttrsFromStyle()(image-tab.js)がcolor/stroke/bukuro/shadowしか拾っておらず、斜体・下線は無視されていた(textPropsのitalicはオプションバーのIボタン専用だった)。

変更内容:

  • スタイルのデータ構造にboldEnabled(太字)とalign(left/center/right)を追加。既存スタイルは未定義→false/'left'扱いで後方互換
  • スタイル編集UI 2箇所に「太字」チェックと「文字寄せ」セレクトを追加: ①テキストスタイルモーダル(text-style-modal.js、#tsm-bold-enable/#tsm-align-select)②フォントタブのスタイルタブ(index.html+19-font-manager.js、#style-bold-enable/#style-align-select。片方のUIにしか無い項目は既存スタイルの再保存で値が消えるため、両方同時に追加する必要がある)。CSSプレビューにもfontWeight/textAlignを反映
  • image-tab.js: _fontStyleAttrsFromStyle()にbold/italic/alignを追加(スタイル適用でtextPropsに反映→_rerenderTextLayerは元々bold/italic/align対応済み)。getSelectedTextStyleInfo()にも追加し、モーダルを開くと選択レイヤーの現在値が初期表示される。オプションバーのB/Iボタン・Alignセレクトは廃止(スタイルモーダルに一本化。縦チェックは残置)
  • 09e _fontMgrApplyStyleAttrsToTextEl: font-weight適用を追加(レイアウトタブのSVGテキストにも太字が効く)。_fontMgrExtractStyleFromTextElにboldEnabled抽出を追加。alignはレイアウトタブのSVGテキストには適用しない(tspan配置が左端基準のためImageタブ専用。コメントで明記)
  • i18n.js: font.boldLabel/font.alignLabel/font.alignLeft/font.alignCenter/font.alignRightを3言語追加
  • 既知の制約: スタイルのunderlineEnabled(下線)はImageタブでは未対応のまま(Canvas2Dに下線描画がなく_rerenderTextLayerに実装がない。レイアウトタブでは有効)

2026-07-11(レイアウトタブ: タブ切り替えで袋文字・影のスタイルフィルタが消えるバグを修正)

ユーザー報告「テキストにスタイルを適用して他のタブから戻ると袋文字の適用が消える」。

原因: 袋文字・影は<defs>内の<filter>+filter="url(#id)"参照で実現されているが、savePanelSvg(07-pages.js)とsaveOverlaySvg(09b)はdefsからclipPathとmask(data-ccc-mask)だけを保存用SVGに持ち回っており、フィルタ定義が保存されなかった。タブを離れて戻るとbuildMergedSvgが保存データからページを再構築するため、フィルタ参照が宙に浮き袋文字・影だけ消える(塗り・線・斜体・下線は要素属性なので無事=「線は残るのに袋文字が消える」報告と一致)。

修正:

  • _collectReferencedFilters(targetSvg, sourceDefs)を07-pages.jsに新設: コンテンツ内のfilter="url(#...)"参照を解決してフィルタ定義をtargetSvgのdefsへ取り込む。savePanelSvg/saveOverlaySvgのシリアライズ直前に呼ぶ。復元側のbuildMergedSvgは元々defsの子を全部マージ(重複IDスキップ)するので保存側の修正のみで完結
  • 同種の漏れも修正: ①異コマへの複製/移動(05-groups-move.js、panelSvgContent直接編集のため移動先defsにフィルタが入らなかった)の2箇所にも同ヘルパーを適用。移動元は要素削除後の再保存で参照されないフィルタが自然に落ちる。②テキスト→PNG(12-text-png-export.js)は defs丸ごと取り込みでフィルタ自体は描画されるが、getBBoxがフィルタの膨張・影を含まないため固定余白20だと見切れる → stroke-width+feMorphology radius+feDropShadow(stdDeviation×3+dx/dy)を余白に加算
  • Kaptureで実機確認: 袋文字適用→Imageタブ→レイアウトタブ復帰で袋文字が保持されること

2026-07-11(レイアウトタブ テキストオプションバーの整理: 色「手動」と★カテゴリボタンを削除)

ユーザー要望「色の手動はなくしスタイルで行いたい。フォント右横のカテゴリ追加/解除★ボタンも削除」。任意色の指定はスタイルモーダルの「塗り」に一本化した。

  • index.html: #color-presetのcustomオプション、#text-color-customカラーピッカー、#font-fav-add-btn(☆/★)を削除。色プリセット(黒/白/赤/青)は残置
  • 09a-balloon-init.js: text-color-customのリスナーとcustom分岐、fontFavAddBtnリスナー、_updateFontFavAddBtn()呼び出し3箇所を削除
  • 09d-balloon-tools.js: _toggleFontFavorite/_updateFontFavAddBtn関数を削除。syncFontFamilyUIの色同期はプリセット一致時のみセレクトを合わせる形に簡素化(プリセット外の色=スタイル由来はセレクトを変更しない)
  • style.css: #font-fav-add-btnルール削除。i18n.js: common.custom/layout.fontFavAddTitle/layout.customTextColorTitleを3言語とも削除
  • フォントのカテゴリ管理自体はフォントタブ(FONTMGR_FAV_CAT等は19/20で現役)とカテゴリ絞り込みセレクトで引き続き可能。Kaptureで★・手動・ピッカーの消滅とcolor-presetが4option(黒白赤青)であることを確認

2026-07-11(レイアウトタブ: 縦書きチェックの選択中テキストへの即時反映+スタイルモーダルのプレビューをSVG化)

ユーザー報告2件。①テキスト選択中に「縦」チェックをON/OFFしても切り替わらない、②スタイルモーダルのプレビューとレイアウト上の実表示が違う。

① 縦書きチェックの即時反映

  • 従来の#text-verticalのchangeはstate.balloon.isVertical(新規作成時のデフォルト)を変えるだけだった。SVGテキストの縦書きはwriting-mode属性+tspan配置(縦: xが列ごとに左へ/y固定、横: dy=1.2em)の両方の切替が必要
  • 09e-text-tool.js: _setTextElVertical(textEl, isVertical, keepCenter)を新設し、applyTextInputの新規/再編集パスの縦横レイアウト構築をこれに統一(再編集時にwriting-modeが切り替わらない既存バグも同時に解消)。keepCenter=trueで切替前後のBBox中心を保って全体シフト(縦書きは親x,yから左へ列が伸びるため、そのままだと行数の多いテキストがコマ外へ飛び出す。回転がある場合は回転中心も追従)
  • 09a-balloon-init.js: changeリスナーで選択中テキストに_setTextElVertical(..., true)→renderTextHandles→savePanelSvg(font-sizeの即時反映と同じパターン)
  • 09d-balloon-tools.js: syncFontFamilyUIで選択テキストのwriting-modeをチェックボックスとstate.balloon.isVerticalに同期(再編集ダイアログで意図せず縦横が変わる既存の食い違いも解消)

② スタイルモーダルのプレビューをSVGレンダリングに変更

  • ズレの原因は2つ: (a)プレビューがCSS(-webkit-text-stroke=中心線基準でfillを侵食+text-shadow)、実表示がSVG(paint-order: stroke fill=外側のみ+feMorphology袋文字+feDropShadow)で描画方式が根本的に違う、(b)previewSizeをpt換算(÷3.528)で渡す一方スタイルの線幅等はSVG単位のまま適用されるため、線の相対太さが約3.5倍ズレる
  • text-style-modal.js: プレビューを2枚重ねdiv→<svg><text>に変更し、実適用と同じ_fontMgrApplyStyleAttrsToTextEl()を通して描画(レンダリングパスが同一なので原理的に一致)。viewBoxはBBox+スタイル余白(袋文字/線/影)でフィット、document.fonts.ready後に再フィット
  • 09a-balloon-init.js: previewSizeをSVG単位のまま渡すよう変更(選択テキストのfont-size実値、未選択時はstate.balloon.fontSize×3.528)。モーダルの「サイズ」欄もSVG単位になる
  • フォントタブ本体のスタイルタブ・アセットパネルFタブのミニプレビューはCSSのまま(_fontMgrRenderTextStylePreviewは残置)。Imageタブから開いた場合もSVGプレビューになる(ImageタブはCanvas描画のため厳密には別物だが、旧CSSプレビューより近い)
  • Kaptureで実機確認: 縦⇔横往復(中心保持)、モーダルのサイズ欄529(=150pt)表示、線30/袋60/影の適用結果がプレビューと同構成であることを確認

2026-07-11(Imageタブ: テキストツールに縦書き機能+G'MIC設定UI)

G'mic起動不能の修正+設定タブ「G'MIC 設定」

  • 7/1のクリーンアップでハードコードパスを除去した際、settings.json(gitignore対象)へのgmicQtPathの設定が漏れて空{}のままだったため、レイアウト/Imageタブ両方でG'MIC GUIが起動しなくなっていた(gmic-tempへの入力書き出しまでは成功)
  • 設定タブに「G'MIC 設定」ブロック(パス入力+保存+ファイル存在チェック警告)を追加。API: GET/POST /api/ccc/local-gmic/settings(通常ルート+ディスパッチテーブル両方に登録)。_gmic_run_guiは実行時に_app_settingsを読むためパス変更の反映に再起動不要(ルート追加自体は要再起動)
  • initGmicSettings()(14-integrations.js)を設定タブ表示時に呼ぶ(01-state.js)。i18nキーsettings.gmic*6個×3言語追加

Imageタブ テキストツール縦書き

  • オプションバーに「縦」チェックボックス(#ie-text-vertical)を追加。textProps.verticalとして保存され、選択中レイヤーへの切替は_applyTextToolChangeToSelection経由で中心保持のまま即反映
  • Canvas2Dに縦書きはないため、TextTool.jsにlayoutVerticalText()(行=列で右から左、長音・括弧類・半角文字は90°回転+実測幅advance、句読点0.3em/小書き仮名0.1em右上寄せ)とdrawVerticalCells()をexport。createLayerData/_measureTextBox/_rerenderTextLayer(袋文字/線/影/塗りをdrawPass()で共通化)に縦分岐を追加
  • フォントタブのプリセット適用/挿入もpreset.isVerticalを反映するように(スタイル単体適用は縦書き非変更のまま)。制約: 縦書き時はalign無視(全列上端揃え)

2026-07-11(Nanobanana: .env読込の実装とAPIキー取り扱いのセキュリティ改善)

ユーザー報告「APIキーの.envファイルをルートフォルダに置いているが読み込めない」。原因は**.envを読み込む処理がそもそも存在しなかった**こと(os.getenv()は実際の環境変数しか見ないため、ファイルを置くだけでは無効だった)。

実装内容(py/ccc.py)

  • _load_env_file()を新設: PLUGIN_DIR/.envをKEY=VALUE形式でパースし、未設定の環境変数にのみos.environへ取り込む(優先順位: 実環境変数 > .env > settings.jsonのnanobananaApiKey)。python-dotenv非依存の最小実装。#コメント行・空行・値の引用符を許容し、メモ帳保存で付きがちなBOM付きUTF-8(utf-8-sig)にも対応
  • APIキーをURLクエリ→HTTPヘッダーに移行: 従来は?key={KEY}をURLに埋めており、ログ・プロキシ・エラーメッセージにキーが残るリスクがあった。generateContent/predict/modelsの3エンドポイントすべてx-goog-api-keyヘッダー送信に変更
  • キー未設定時のガード: generate/modelsの両ハンドラで、Google APIに投げる前に「.envにNANOBANANA_API_KEYを記載しComfyUIを再起動」という具体的な案内をエラー返却するようにした(従来はGoogleの403がそのまま出て原因が分かりにくかった)
  • .gitignoreを新設: .env・settings.json(ローカルパス設定)・__pycache__/・output/・assets/assets.json・*.bak
  • 確認済みの安全性: 静的配信ルート(/ccc_static等)はstatic/assets/output配下のみで**.envはWeb経由で取得不可**。/api/ccc/nanobanana/keyは従来どおりマスク値のみ返す

検証・注意

  • py_compileOK。_load_env_fileと同一ロジックで実.envのパースを検証し、NANOBANANA_API_KEY(39文字)が正しく取得されることを確認(キー本体はマスク表示で確認)
  • py側の変更はComfyUI再起動後に有効。再起動後、Nanobananaタブのキー状態表示(マスク表示)で読み込みを確認できる

追記: 429エラーの顛末(同日解決)

実装後にユーザーから「新キー取得・従量課金購入済みなのに429」の報告。切り分けの結果、.envが3月から未更新で旧キーのままだった(429=旧キーの無料枠枯渇、その後旧キー無効化で400に変化)。OS環境変数には残存なし、指定3モデル名はモデル一覧APIで実在確認済み。ユーザーが.envを新キーに更新しComfyUI再起動後、/api/ccc/nanobanana/keyのマスク値が新キー(53文字)に一致し、アプリ経由のgemini-3.1-flash-lite-image生成が成功(status ok・1枚)することをエンドツーエンドで確認。新実装(.envローダー+ヘッダー認証+新モデル名)の全経路が実運用で検証された。


2026-07-11(Nanobananaタブ: モデル一覧を更新)

旧一覧(gemini-3.1-flash-image-preview / gemini-3-pro-image-preview / gemini-2.5-flash-image)を、ユーザー指定の3モデルに更新。

  • templates/index.html の #nanobanana-model セレクト: gemini-3.1-flash-lite-image / gemini-3.1-flash-image / gemini-3-pro-image(この順、先頭がデフォルト選択)
  • py/ccc.py の handle_nanobanana_generate のフォールバック既定値も gemini-3.1-flash-lite-image に更新(フロントは常にmodelを送信するため実質は保険。py側の変更はComfyUI再起動後に有効)
  • モデル選択の永続化は無し(nanobanana.jsはセレクト値を読むだけ)のため他に波及箇所なし。Kaptureでセレクトの3option反映を確認済み

2026-07-11(フォントタブ: 左パネルのフォント一覧をフラット表示化)

ユーザー要望「左のフォント一覧ペインの英語/ひら・カナ/数字…のフォルダ展開表示をやめたい(検索でフィルタリングできるため不要)」。従来の「大カテゴリ(英語A-Z/ひら・カナ/数字/その他)→頭文字(A〜Z/あ行〜わ行)」の2段階折りたたみツリーを廃止し、件数表示+フォント項目の単純なフラット一覧に変更した。

  • 19-font-manager.js: _fontMgrRenderList()をフラット描画に書き換え。専用だった_fontMgrClassify(かな行分類)・_FONTMGR_CATS(大カテゴリ定義)・_fontMgrSortSubsを削除。_fontMgrGroupOpen/_fontMgrToggleGroupはアセットパネル「F」タブ(09e _fontAssetBuildGroup)が共用しているため残置(コメントを現状に合わせて更新)
  • i18n.js: 前セッション(Phase 2③)で追加したばかりのfont.catEn/font.catJa/font.catOther/font.subOtherが未使用になったため削除(628キー×3言語)。font.charNumbersはHTML側チェックボックスで使用中のため残置
  • style.css: サブグループヘッダー.fontmgr-group-header系3ルールを削除。.fontmgr-cat-header・.fontmgr-group-arrow/label/countは「F」タブが使用中のため残置(コメントを更新)
  • 検証: node --check・キー突合・削除定義の残存参照ゼロを機械確認。Kapture実機でシステム506件のフラット表示、名前検索「meiryo」→「2件」に絞り込み、クリック選択→右パネル/プレビュー反映、コンソールエラーなしを確認

2026-07-11(フォントタブ: カテゴリ「お気に入り」を予約済みカテゴリ化)

ユーザー要望「Fontタブのグループ(カテゴリ)のお気に入りを、ページ管理のstock/ゴミ箱と同様の予約済みグループにしたい」。従来はお気に入りカテゴリが空のとき(全カテゴリ0件時)のみ自動作成され、他のカテゴリがあれば削除したまま復活しなかった。

実装内容

  • 19-font-manager.js: FONTMGR_FAV_CAT = 'お気に入り'(データキーはUI言語によらず固定)、FONTMGR_RESERVED_CAT_NAMES(3言語表示ラベル 'お気に入り'/'Favorites'/'收藏' を新規作成禁止)、_fontMgrCatLabel(cat)(予約カテゴリのみfontsel.defaultCategoryで表示翻訳)、_fontMgrCatNames()(予約カテゴリ先頭固定の一覧)を新設。_fontMgrLoad()で「無ければ復元して保存」する常設化(従来の「全カテゴリ0件時のみt()名で作成」を置換——言語別キーが乱立する問題も同時に解消)
  • 20-font-presets.js: カテゴリ追加に予約名ガード(alert font.reservedCategoryName)、カテゴリ削除に予約カテゴリガード(alert font.reservedCategoryDelete)。カテゴリタブ・右パネル2セレクトを_fontMgrCatNames()/_fontMgrCatLabel()経由に変更(お気に入り先頭・表示翻訳)
  • 09d-balloon-tools.js: レイアウトタブの#font-fav-cat-selectも同様に先頭固定+表示翻訳。★ボタンの追加先が「すべて」選択時は従来の「先頭カテゴリ」から予約済みカテゴリ「お気に入り」固定に変更(挙動が予測可能になる)
  • i18nキー+2(632×3言語): font.reservedCategoryName/font.reservedCategoryDelete
  • _fontMgrLoad()は09a(レイアウト初期化・DOMContentLoaded)でも呼ばれるため、フォントタブを開かなくても起動時に復元される

既知の制限

  • Imageタブ(image-tab.js)はlocalStoragefontmgr_favoritesを直接読んでカテゴリ名を生キーで表示するため、en/zh UIでは予約カテゴリが「お気に入り」のまま表示される(実害は表示のみ。Phase 2④のImage連携i18n時にwindow._fontMgrCatLabel経由に直すのが自然)
  • 既存データに'Favorites'/'收藏'という名前のユーザーカテゴリが万一あっても統合はしない(新規作成のみ禁止)

検証

node --check 4ファイルOK・キー632×3言語一致・t()参照未定義ゼロ。Kapture実機: 削除済み状態から起動→「お気に入り (0)」がカテゴリタブ先頭に復元、削除ボタン→予約alert(en「"Favorites" is a reserved category...」)、「お気に入り」名での新規作成→予約名alert、日本語UIでも先頭表示・コンソールエラーなし。


2026-07-11(多言語化(i18n) Phase 2 第3弾:フォントマネージャー(19, 20)のt()化完了)

Phase 2第3グループ「フォントマネージャー」の2ファイル(19-font-manager.js / 20-font-presets.js)を完了。キー数は608→630×3言語(+22)。置換はPython完全一致replace(32ルール、全件一致)。node --check+キー集合相互差分ゼロ+t()参照221キー全定義済みを機械検証、Kapture実機で英語UI(フォントリストのカテゴリヘッダ「English A-Z」・件数「31 fonts」・右パネル「(No font selected)」・スタイル/プリセットセレクト「(New)」「(No style)」・スタイル削除alert)と日本語復帰・コンソールエラーなしを確認済み。

対応ファイルと内容

  • 19-font-manager.js: デフォルトお気に入りカテゴリ(fontsel.defaultCategoryを計算プロパティキーで使用)、大カテゴリラベル_FONTMGR_CATS(font.catEn/font.catJa/font.charNumbers再利用/font.catOther)、「他」サブグループ、リスト空表示・件数、右パネルのフォント未選択・タグチップtitle、スタイル保存/削除のalert/confirm
  • 20-font-presets.js: スタイル/プリセットセレクトの先頭option(font.newOption/font.noStyleOption=Phase 1定義済みキー再利用)、プリセット保存/削除、カテゴリタブ「すべて」(layout.fontCatAll再利用)、システムフォント読込ステータス(common.loading/fontsel.fetchFailed再利用)、カテゴリ追加/削除のprompt/alert/confirm、適用ボタンalert(textTool.selectTextFirst再利用)

設計判断

  • フォント見本テキストは翻訳対象外(意図的): _FONTMGR_PREVIEW_TEXTS(いろは・カタカナ・千字文)、_FONTMGR_SENT_H/V(イーハトーヴォ)、スタイルプレビュー既定文字「あ亜Aa1」は、日本語グリフの見本という目的そのものなので言語によらず据え置き
  • かな行ラベル(あ行〜わ行/ア行〜ワ行)も据え置き: フォント名の頭文字分類という日本語固有概念であることに加え、_fontMgrSortSubsがラベル先頭文字のorder.indexOfでソートしており翻訳すると並び順が壊れるため
  • デフォルトカテゴリ名は永続化データだが、Phase 2の方針どおり「新規生成時のみ現在言語」(既存の保存済みfontmgr_favoritesは移行しない)

残タスク(Phase 2続き)

④Image連携(image-tab.js, image-tab/*.js)/Nanobanana(nanobanana.js)/PixiFX(15) → ⑤スクリプト(21) → ⑥3Dポーズ(23)/G'MIC・Eagle(14)/その他(00-03, 05, 12-14, 16, 18)。その後Phase 3(ヘルプ_HELP_DATA)・Phase 4(バックエンド)。


2026-07-11(多言語化(i18n) Phase 2 第2弾:ページ/作品管理(07, 10, 11a, 11b)のt()化完了)

Phase 2第2グループ「ページ/作品管理」の4ファイル(07-pages.js / 10-output-pages.js / 11a-work-manager.js / 11b-page-manager-tab.js)を完了。キー数は538→608×3言語(+70)。置換はPython完全一致replace(91ルール、全件expected count一致)。node --check+「3言語キー集合の相互差分ゼロ」「JS内t()参照194キー全定義済み」を機械検証、Kapture実機で英語UI(作品カード・グループ一覧・ゴミ箱表示・予約グループ名alert・出力ページ一覧・レイアウト空メッセージ)と日本語復帰・コンソールエラーなしを確認済み。

対応ファイルと内容

  • 07-pages.js: 保存/削除/作成のalert・confirm・prompt(コマの線幅)、ページセレクト先頭option、アセットパネル「P」タブ空メッセージ、レイアウトプレビューのエラー表示(page.msg* 16キー+layout.msgNoImageSelected)
  • 10-output-pages.js: フィルタバーの作品/グループラベル、ページ一覧空表示(asset.noPages再利用)、番号入力title、画像取込のalert(page.workLabel/page.orderInputTitle/page.msgNotImageFile/page.msgImportFailed)
  • 11a-work-manager.js: TRASH_GROUP_LABEL(page.trashLabel)、WORK_SIZE_PRESETS 6種+カスタムoption、作品新規作成ダイアログのalert/prompt/confirm、「作業中」ラベル・バッジ・作品カード情報行、グループ一覧の(nページ)、テンプレート挿入系alert(page.preset*/page.activeWorkLabel等 20キー)
  • 11b-page-manager-tab.js: グループ追加/リネーム/削除・作品削除・ページ移動/複製/一括削除/完全削除のalert/confirm、リネームモーダルHTML、連番リネームprompt/confirm、サイドパネル動的ボタン(復元/移動・完全削除/削除(ゴミ箱))、プロパティ表示、ゴミ箱バッジ削除: 日時(page.* 30キー)

設計判断・注意点

  • TRASH_GROUP_LABELはロード時にt()で確定する(i18n.jsが最初に読み込まれるため可能)。予約グループ名ガードRESERVED_GROUP_NAMESは、言語切替で別言語のラベル名グループが作れてしまわないよう3言語のラベル('ゴミ箱'/'Trash'/'回收站')をリテラルで全て含めた
  • 確認ダイアログのラベル合成: 単一選択「名前」と複数選択選択中の N ページをpage.quotedName/page.selectedPagesLabelで作り、page.confirmTrash/page.confirmPermanentDelete(label引数)に渡すネストt()パターンを導入(削除系4箇所を2キーに集約)
  • 既存キーの再利用: tmpl.selectTemplate/tmpl.notFound/tmpl.alreadyExists/tmpl.selectGroup/tmpl.newGroupNamePrompt/tmpl.renameOk/tmpl.enterName/tmpl.renameFailed/tmpl.groupLabel/tmpl.groupNone/common.cancel/asset.noPages/asset.noTemplates/page.noWorks/page.groupSelectOption/page.moveGroupOption/page.moveBtn/page.moveBtnTitle/page.deleteTrash/page.exportEmptyMessage/layout.notSelected/layout.msgImageLoadFailed
  • 11bの出力プレビュー空メッセージは旧文言が「左のリストから〜」でHTML静的側(page.exportEmptyMessage「右のリストから〜」)と不整合だったため、再利用により「右」に統一(ページ一覧は実際に右側にある)
  • console.log/console.errorのログ文言は従来どおり翻訳対象外

残タスク(Phase 2続き)

③フォントマネージャー(19, 20) → ④Image連携(image-tab.js等)/Nanobanana/PixiFX(15) → ⑤スクリプト(21) → ⑥3Dポーズ(23)/G'MIC・Eagle(14)/その他(00-03, 05, 12-14, 16, 18)。その後Phase 3(ヘルプ_HELP_DATA)・Phase 4(バックエンド)。


2026-07-10(多言語化(i18n) Phase 2 第1弾:レイアウトタブ系JSのt()化完了)

Phase 1完了(下記エントリ)に続き、Phase 2(JS動的生成文言のt()化)に着手。計画の第1グループ「レイアウトタブ系」の全ファイルを完了した。キー数は538×3言語(Phase 1完了時437→+101)。JS内のt('...')参照116キーすべての定義をNodeスクリプトで機械検証、Kapture実機で英語UIでの動的描画(レイヤーパネル・コマセレクト・テンプレートグループフィルタ)とコンソールエラーなしを確認済み。

対応ファイルと内容

  • 08-panels-images.js: コマセレクト生成(common.panelName/common.overlayFull)、画像挿入系alert 7種(layout.msg*)
  • 06b-template-manager.js / 06c-template-wizard.js: テンプレート作成/削除/リネームのalert/confirm/prompt、リネームモーダルHTML、グループセレクト再構築、サイドパネル表示(tmpl.* 24キー)
  • 04a-mask-core.js: マスク対象ラベル・ステータス表示・alert/confirm、マスク既定名の生成(mask.* 14キー)
  • 04b-layer-panel-render.js: レイヤーパネルの全行テンプレート(オブジェクト既定名5種・ボタンtitle約20種・コマ/オーバーレイ行・マスク行、layer.*)
  • 09a/09c/09d/09e: テキスト保存・画像変換alert、フォント選択optgroup(システム/カテゴリ)、スクリプト挿入・スタイル適用のalert、Fタブ連携アセットグリッド(textTool.*/fontsel.*)
  • 17a/17b: レイヤー描画のステータス表示一式(draw.* 10キー)
  • 06a/17cには対象文字列なし、09bは前回対応済み

設計判断(Phase 2で踏襲すること)

  • 補間が必要な文言は関数値キー('tmpl.created': (name) => \テンプレート "${name}" を作成しました。`)にし、呼び出し側はt('tmpl.created', name)。i18n.jsのt()がtypeof val === 'function'`で自動処理する(Phase 0からの仕様、今回初めて本格使用)
  • 永続化されるデフォルト名(レイヤーのdataset.name「フキダシ N」、マスクのdata-ccc-mask-name「マスク N」、フォントカテゴリ既定名「お気に入り」等)も生成時にt()を通す方針にした。既存の保存済み名はそのまま表示され、新規作成分だけ現在のUI言語の名前になる(データ移行はしない)
  • 置換は「元文字列の完全一致replace」をPythonスクリプトで一括実行し、置換漏れ(NOT FOUND)を検出する方式。長文・テンプレートリテラル内の置換も安全にできた
  • 検証: node --check+「JS内t('key')全抽出→ja辞書との突合」をNodeワンライナーで実施(HTML側data-i18nチェックと合わせて再利用可能)

残タスク(Phase 2続き)

計画の残りグループ: ②ページ/作品管理(07, 10, 11a, 11b) → ③フォントマネージャー(19, 20) → ④Image連携(image-tab.js等)/Nanobanana/PixiFX(15) → ⑤スクリプト(21) → ⑥3Dポーズ(23)/G'MIC・Eagle(14)/その他(00-03, 05, 12-14, 16, 18)。その後Phase 3(ヘルプ_HELP_DATA)・Phase 4(バックエンド)。


2026-07-10(多言語化(i18n) Phase 1完了:templates/index.html 静的部分の全面3言語対応)

前回(下記エントリ)の続き。templates/index.htmlの残り全セクションにdata-i18n/data-i18n-title/data-i18n-placeholder属性を付与し、static/js/i18n.jsのja/en/zh各ブロックに対応キーを追加した。これでPhase 1(静的HTML)は完了。最終キー数は3言語とも437キー(開始時点78キー→+359)。Node実行スクリプトで「3言語のキー集合が完全一致」「HTMLが参照する全キーが定義済み」を機械検証し、Kaptureブラウザ実機でja/en/zhの3言語切替表示・コンソールエラーなしを確認済み。

今回対応したセクションとキー名前空間

  • レイアウトタブ本体(layout.*、最大ボリューム・約130キー): ページコントロール/ドロー(ボックス・SVG色変更・SVG→PNG)/フキダシ(h2パラメータ含む)/テキスト/画像(Processing・G'MIC・PixiJS FX)/マスク/3Dポーズの全サブタブ
  • Imageタブ(image.*): 元々英語主体のため日本語箇所のみ(Close/Save系ボタンのtitle、統合、全体不透明度、調整レイヤー12種、レイアウトに送る)
  • フォントタブ(font.*): ソースタブ・検索・プレビュー5タブ(基本/文章/カスタム/カテゴリ一覧/スタイル)・スタイル/プリセット作成パネル・右パネル(タグ・カテゴリ管理)
  • ページタブ(page.*): 作品管理/テンプレート/出力の3サブタブ全体(サイドパネル群・出力コントロール・ページ一覧含む)
  • Nanobananaタブ(nb.*)/スクリプトタブ(script.*)/ヘルプ検索欄(help.searchPlaceholder)
  • レイヤーサイドパネル(layer.*)/ダイアログ3種(dialog.*: 作品新規作成・テキスト入力、wiz.*: テンプレートウィザード)
  • 頻出語彙はcommon.*に集約(undo/flipH/flipV/black/white/red/blue/custom を今回追加)し、既存キー(common.save等)も積極的に再利用して重複キーを作らない方針を維持

実装上の注意点(今後の同種作業で必須の知識)

  • applyI18nToHtml()はtextContentを丸ごと書き換えるため、<input>を内包する<label>はテキスト部分を<span data-i18n>で包んでから属性を付ける必要がある(今回フォントタブ・マスクサブタブ・ウィザード等で多数実施)。子要素を持つ要素に直接data-i18nを付けると子要素が消える
  • JSが初期化時に上書きする箇所はdata-i18nだけでは不十分。今回は最小限のJS側t()化を2箇所だけ実施: 09b-balloon-shapes.jsの編集: ON/OFF(layout.editModeOn/Off)と06c-template-wizard.jsの分割モードヒント(wiz.hintAll/hintSingle)。それ以外のJS動的生成文言(ステータス表示、セレクト再構築、alert等)はPhase 2の対象として未着手
  • display:noneの互換用select(#layer-draw-shape・#shape-type)は不可視のため意図的にスキップ
  • <img alt>はapplyI18nToHtmlが未対応(今回1箇所のみ・実害軽微のため見送り。必要になったらdata-i18n-alt対応を追加する)
  • 検証はNodeワンライナーでLANGUAGESをevalし、ja/en/zhキー集合の相互差分とdata-i18n(-title|-placeholder)参照キーの未定義を機械チェックするのが確実(このセッションで3回実施し、都度ゼロを確認)

残タスク(Phase 2以降、計画は前回エントリ+プランファイル参照)

  • Phase 2: JS動的生成コンテンツのt()化(レイヤーパネル項目・alert/confirm・フォントマネージャー動的リスト・Nanobananaステータス・スクリプトタブのテーブル等、44ファイルに散在)
  • Phase 3: 22-help-tab.jsの_HELP_DATA長文翻訳
  • Phase 4: py/ccc.pyのエラーメッセージ

2026-07-10(多言語化(i18n)着手:基盤構築 + Phase 1一部)

ユーザー要望「i18n化したい、作業計画を立ててほしい」。対象言語は英語+中国語(日本語含め3言語)、翻訳文はClaudeが作成、段階的に進める方針で合意。

事前調査で判明した重要事項

  • 姉妹プロジェクト ComfyUI-Workflow-Studio(comfyUI-wf-maneger/ComfyUI-Workflow-Studio)に完成済みのi18nシステム(static/js/i18n.js、en/ja/zh、t()関数、設定タブの言語セレクタ、location.reload()方式)が存在し、これを本プロジェクトの制約(classic <script>共有グローバルスコープ、ESモジュールではない)に合わせて移植する形にした。
  • templates/index.html(1,658行)は静的な日本語文字列が300〜450以上(ボタン・ラベル・option・title・placeholder)。i18n機構は元々皆無だった。
  • static/js/main/*.jsほか計44ファイルにも日本語が広く散在(特に22-help-tab.jsの_HELP_DATAは長文プローズで別枠)。JS側の変換はPhase 2以降(今回は未着手)。

採用したアーキテクチャ

  • 新規 static/js/i18n.js(classic <script>、他の全スクリプトより前に読み込み): LANGUAGES = {ja, en, zh}(ドット区切り名前空間キー、例 nav.layout, settings.eagleUrl)、t(key, ...args)(フォールバック: 現在言語→ja→キー自身)、getLang()/setLang()(localStorageキーccc_ui_langに永続化まで内包)、getLanguageOptions()、applyI18nToHtml(root)([data-i18n]/[data-i18n-placeholder]/[data-i18n-title]属性を走査して適用する汎用walker)
  • 静的HTML(templates/index.html)はdata-i18n系属性方式(参考実装のid対応表手書き方式ではなく、文字列数が多いため属性方式を採用)
  • static/js/main/01-state.jsのDOMContentLoaded冒頭でapplyI18nToHtml()とinitI18nSettings()(設定タブの言語セレクタ変更→setLang()→location.reload())を呼ぶ
  • 設定タブに言語セレクタ(日本語/English/中文)を新設

今回実施した範囲(Phase 0 + Phase 1の一部)

  • Phase 0(基盤+パイロット、完了): i18n.js基盤一式、トップナビ9タブボタン+ws関連2ボタン、設定タブ全体(新設の言語セレクタ含む既存Eagle設定一式)。ja/en/zh 3言語の翻訳を実際に作成し、data-i18n属性・t()関数の両方式が動くことをKaptureで実機検証済み(言語切替→リロード→表示反映→localStorage永続化まで確認)。
  • Phase 1一部(完了): 全タブ共通の左サイドバー「アセットパネル」と、レイアウトタブ専用の「ツールペイン」(ドロー/フキダシ/テキスト/画像/マスク/3Dポーズの切替ボタン)、計約30文字列をdata-i18n化・3言語翻訳。構文チェック・ブラウザロード確認(コンソールエラーなし)まで完了。

未着手(次回に持ち越し)

Phase 1の残り: レイアウトタブ本体(最大、約470行)・Imageタブ・フォントマネージャータブ・Nanobananaタブ・スクリプトタブ・設定タブ残り・ヘルプタブ静的UI部分・テンプレート作成ウィザード。その後のPhase 2(JS動的生成コンテンツのtabごとのt()化)・Phase 3(ヘルプ本文の翻訳)・Phase 4(バックエンドエラーメッセージ)は計画のみでまだ着手していない。詳細な分割案はプランファイル参照。

Why: ユーザーの明示的な「段階的に進めたい」意向を尊重し、1回のセッションで全体を変換しようとせず、基盤構築+実証可能な最小範囲でまず完成させ、動作確認してから次に進む形にした。

How to apply: 次回i18n作業を再開する際は、まずtemplates/index.htmlのレイアウトタブ本体(<section id="layout-tab">、行136付近から)から着手する。手順は既に確立済み: ①対象セクションの日本語文字列にdata-i18n/data-i18n-placeholder/data-i18n-title属性を付与、②static/js/i18n.jsのLANGUAGES.ja/.en/.zh各ブロックに同じキーで追記(名前空間はタブ名を先頭に、例: layout.xxx)、③node --checkで構文確認、④Kaptureまたは実機で言語切替検証。既存のcommon.save/common.cancel/common.deleteなど汎用キーは使い回すこと(新規に同義語キーを作らない)。

2026-07-10(重大バグ修正:レイアウトタブの継続利用でメモリリーク→クラッシュ)

ユーザー報告: Chromeタブのメモリが600MB→4000MB+まで際限なく増加し、フレーム表示がどんどん遅くなり、最終的に「Not enough memory / Out of Memory」でタブがクラッシュする。作品を開いてオブジェクトを選択するだけで発生。

原因

renderLayoutTab()(07-pages.js)は、Undo・ページ切替・パネル移動・画像挿入・フキダシパネル切替など通常の編集操作のたびに呼ばれ、そのたびに以下5つのinit*系関数を再実行して新しいSVGを構築し直す:

  • initImageManipulation()(08-panels-images.js)
  • initGroupManipulation()(06a-polygon-geometry.js)
  • initDrawShapeManipulation()(17c-layer-draw-handles.js)
  • initBalloonTools()(09d-balloon-tools.js)
  • initTextTools()(09e-text-tool.js)

これら5関数はいずれも内部でdocument.addEventListener(...)/window.addEventListener(...)(mousemove・mouseup、計10個)を登録していたが、対応するremoveEventListenerが一切なかった。document/windowは再生成されないため、呼ばれるたびにリスナーが純粋に積み上がり、各リスナーのクロージャが「その回にレンダリングされた(もう画面から外れた)古いSVGツリー全体」を握ったまま解放されない。操作を続けるほどリスナー数とメモリ保持量が線形に増え続け、かつ毎回のmousemoveイベントで積み上がった全リスナーが実行されるため描画も徐々に重くなる——報告された症状と完全に一致する。

同じ「DOM移動+再初期化」パターンを使う_layerDrawAttachOverlay/_maskAttachOverlay/_tmplWizAttachCanvasEventsは、detach→attachの順で正しくremoveEventListenerしていたため対象外(このパターンとの比較で問題箇所を特定)。

修正

5ファイルすべてで同じ形の修正: 各document/windowリスナーを無名関数から名前付き変数に変更し、モジュールスコープの変数に保持。関数の先頭で「前回登録分があればremoveEventListenerしてから」新規登録するガードを追加した。

  • static/js/main/08-panels-images.js: _imgManipDocMouseMove/_imgManipDocMouseUp/_balloonManipDocMouseMove/_balloonManipDocMouseUp
  • static/js/main/06a-polygon-geometry.js: _groupManipWinMouseUp
  • static/js/main/17c-layer-draw-handles.js: _drawShapeManipWinMouseUp
  • static/js/main/09d-balloon-tools.js: _balloonToolsDocMouseMove/_balloonToolsDocMouseUp
  • static/js/main/09e-text-tool.js: _textToolsDocMouseMove/_textToolsDocMouseUp

動作確認

全5ファイル構文チェック通過。ブラウザでの再読み込み・表示は正常(KaptureのdomSizeメトリクスは今回の調査中に実DOM量と無関係な異常値を示すことが判明したため参考にせず、実機のタスクマネージャーでの確認をユーザーに依頼)。

Why: ユーザー報告の緊急バグ対応。同種のバグ(init*系関数を再入可能にする際、document/window等の永続オブジェクトへのイベント登録をdetach処理なしで追加する)が将来また混入しないよう、新規に同パターンの関数を書く際は必ずdetach-before-attachにすること。

追記(同日): 上記修正だけでは改善せず、真因は別にあった

ユーザーがPC再起動まで行った上で再検証したが症状が改善しないとの報告。上記のイベントリスナー積み上がりは実在するバグで修正自体は正しいが、主因は別にあった。

真因: 画像/フキダシの「クリック選択」だけで毎回ページ全体をディープクローンしていた

08-panels-images.jsの画像本体・フキダシ本体のmousedownハンドラが、実際にドラッグしたかどうかに関係なく選択した瞬間にpushHistory()を無条件で呼んでいた。pushHistory()(07-pages.js)はstate.activePage.panelsをJSON.parse(JSON.stringify(...))で丸ごとディープクローンしてstate.historyに積む処理で、panelsには画像がbase64データURLとして埋め込まれたSVG文字列(panelSvgContent)が含まれる。画像が多いページでは1回のクローンが数十MB規模になり得、state.historyは20件で頭打ちにしているとはいえ、クリックのたびに数十MB規模のstringify/parseが走るため、素早く連続してオブジェクトを選択・確認するだけで数百MB〜数GB規模のメモリ圧迫と処理遅延が発生していた。ユーザー報告「作品を開いてオブジェクトを選択しただけで重くなる」と完全に一致する。

比較のため他のオブジェクト種別(テキスト・グループ・図形描画)の選択ハンドラを確認したところ、いずれも選択だけではpushHistoryを呼んでおらず、画像とフキダシの2箇所だけがこのバグを持っていた。マスク編集の_maskPointerDown(04a-mask-core.js:460)は元々if (!_maskState.historyPushed) { pushHistory(); _maskState.historyPushed = true; }という「実際に描画し始めた最初の1回だけ積む」正しいパターンを使っており、今回はこれと同じパターンを画像・フキダシにも適用した。

修正

static/js/main/08-panels-images.js:

  • 画像ドラッグ: mousedown時のpushHistory()を削除し、imgHistoryPushedフラグを追加。実際に動いた最初のmousemoveでのみpushHistory()を呼ぶよう変更
  • フキダシドラッグ: 同様にballoonHistoryPushedフラグを追加し、mousedown時の無条件pushHistory()を削除、最初のmousemoveでのみ積むよう変更
  • リサイズ/回転パス(.image-handle経由)はpushHistory自体を元々呼んでおらず未変更(Undo非対応は既知の別課題、今回はスコープ外)
  • mouseup時のsavePanelSvg(IndexedDB書き込み)は無条件のまま維持(リサイズ/回転との整合性を保つため、あえてガードしなかった)

動作確認

構文チェック通過、ブラウザでの起動・表示は正常。

Why: 「選択だけで重くなる」というユーザー報告の再現条件から、renderLayoutTab経由のリスナー積み上がり(レンダリング系操作で発生)よりも、画像/フキダシの単純クリック選択で毎回発生するこちらの方が主因である可能性が高いと判断し、両方を修正した。


2026-07-10(リリース前チェック:セキュリティ修正 + main.js モジュール分割)

セキュリティ/不具合修正

  • py/ccc.py: PLUGIN_DIR のimport漏れを修正(Eagle連携のローカルパス追加・G'MICサーバー起動がNameErrorでクラッシュしていた)
  • py/ccc.py handle_save_group_asset: グループ名に単体の..を渡すことでassets/の1つ上へ書き込めるパストラバーサルを、handle_delete_assetと同様のresolve()ベース検証で修正
  • py/ccc.py _gmic_run_gui: G'MIC実行ファイルパスのデフォルト値にハードコードされていた開発者のユーザー名パスを除去し、未設定時は明確なエラーメッセージを返すよう変更
  • static/js/nanobanana.js: refreshApiKey()がバックエンドから返らないdata.keyを参照していたため、生成ボタンが常に「APIキー未設定」判定になり機能していなかったバグを修正(statusで判定するよう変更)
  • static/js/main/: 外部由来(共有アセット・テンプレート・プロジェクトファイル)のSVGをDOMに挿入する箇所(アセット挿入・ページ合成SVG描画)にsanitizeSvgTree()を追加し、<script>/<foreignObject>/on*属性/javascript:URLを除去してXSSを防止
  • static/js/main/: レイヤーパネルの名前表示(画像・図形・テキスト・グループ・マスクレイヤー名、テンプレート名・グループバッジ)計8箇所の未エスケープinnerHTML挿入を_escHtml()でエスケープ

main.js モジュール分割(18,313行 → 24ファイル)

保守性向上のため、static/js/main.jsを責務ごとにstatic/js/main/00-db.js〜23-pose3d-bridge.jsの24ファイルに分割。既存の// ====区切りコメントをそのまま分割点として採用し、内容の並び替え・変更は一切行っていない(分割後の再連結が原本と完全一致することをdiffで確認済み)。

  • 引き続き<script>(非module)として読み込み、グローバルスコープ共有はそのまま維持(動作リスクを最小化するための意図的な選択。ESモジュール化はスコープ外)
  • 読み込み順はtemplates/index.htmlの<script>タグ順に厳密依存(元main.js内のコード実行順序と同一になるよう分割)
  • 各ファイル冒頭に「元main.jsの行範囲」「主なトップレベル定義一覧」をヘッダコメントとして付与。将来ESモジュール化する際の境界・importリストの土台として使える
  • 旧main.jsはstatic/js/main.js.bakとして保持(ユーザー側バックアップに加えた保険)
  • ファイル構成: 00-db(IndexedDB) 01-state(状態管理/初期化/タブ管理) 02-assets(アセット管理) 03-layers-panel 04-mask-layers 05-groups-move 06-template-wizard 07-pages 08-panels-images 09-balloons(フキダシ、最大3,119行) 10-output-pages 11-works(作品管理) 12-text-png-export 13-export-pdf-epub 14-integrations(Eagle/G'MIC/WorkflowStudio) 15-pixifx-bridge 16-processing-edit-tabs 17-layer-draw 18-svg-color-png 19-font-manager 20-font-presets 21-script-tab 22-help-tab 23-pose3d-bridge

動作確認

  • 全24分割ファイル・ccc.pyともに構文チェック(node --check / python -m py_compile)通過
  • 分割ファイルを再連結したものが元main.jsと完全一致することをdiffで確認(欠落・重複・順序変更なし)
  • Kaptureで実ブラウザ起動確認:起動ログがDB connected→Plugin Initializing...→Plugin Initialized→NanobananaManager Initializing...の順で正常出力、コンソールエラーなし(Kapture拡張自身のMessage exceeded maximum allowed sizeログを除く)
  • 作品一覧→ページ選択→レイアウトタブ描画(コマ・吹き出し・レイヤーパネル)、Imageタブ切替、フォントタブ切替(プレビュー描画)まで一通り操作し、いずれも正常動作を確認

Why: 近日リリースに向けた全体チェックの一環。ユーザー要望「リファクタリングの必要性」「セキュリティチェック」への対応。バックアップ取得済みであることを確認の上でモジュール分割を実施。

追加クリーンアップ + さらなるファイル分割(同日追加作業)

上記の続きとして、リリース前クリーンアップと大きい分割ファイルのさらなる細分化を実施。

  • クリーンアップ: output/gmic-temp/配下の蓄積一時ファイル(7.8MB、34件)を削除、assets/test/(テスト用SVG5枚)を削除、__pycache__/を削除。assets/test削除後は/api/ccc/refresh-assetsを叩いてassets.jsonを再生成し、testフォルダのエントリが残らないことを確認
  • 大きい分割ファイルのさらなる細分化: 1,400行を超えていた5ファイルを、既存のセクション区切り/関数境界を安全な分割点としてさらに分割(内容の並び替え・変更は一切なし、各段階でdiffによる完全一致を確認)
    • 04-mask-layers.js(1,553行) → 04a-mask-core.js(マスク機能ロジック) + 04b-layer-panel-render.js(renderLayerPanel本体)
    • 06-template-wizard.js(1,713行) → 06a-polygon-geometry.js + 06b-template-manager.js + 06c-template-wizard.js
    • 09-balloons.js(3,119行) → 09a-balloon-init.js + 09b-balloon-shapes.js + 09c-balloon-handles.js + 09d-balloon-tools.js + 09e-text-tool.js
    • 11-works.js(1,462行) → 11a-work-manager.js + 11b-page-manager-tab.js
    • 17-layer-draw.js(1,574行) → 17a-layer-draw-input.js + 17b-layer-draw-commit.js + 17c-layer-draw-handles.js
    • 合計24ファイル→34ファイルに(templates/index.htmlの<script>タグ順も追従して更新)
  • ハマった点: サブ分割時、分割元ファイル(例: 04-mask-layers.js)自身が持っていた9行のヘッダーコメントが、最初のサブファイル(04a-mask-core.js等)の実コンテンツとして紛れ込み、存在しないファイル名を参照する重複ヘッダーが残っていた(動作への影響はないが紛らわしい)。5ファイルとも該当ブロック(8行)を削除して解消。分割済みファイルをさらにサブ分割する際は、親ファイル自身のヘッダーが子ファイルの先頭に埋め込まれる点に注意すること
  • 未着手のまま残したもの: 04b-layer-panel-render.jsのrenderLayerPanel()は680行の単一巨大関数(画像/図形/テキスト/グループ/マスクレイヤーの描画ロジックを内部クロージャで実装)で、ファイル分割の安全な境界にできなかった。将来的に関数内部の構造化(レイヤー種別ごとの描画をヘルパー関数に切り出す等)をする場合は、動作リスクのあるリファクタリングになるため別途計画すること

動作確認(追加分)

  • 34ファイル・ccc.pyともに構文チェック通過
  • 34ファイル全ての再連結が元main.js.bakと完全一致することをdiffで再確認(クリーンアップ後の最終形)
  • Kaptureで実ブラウザ再確認:起動ログ正常・コンソールエラーなし。作品一覧→ページ選択→開く→レイアウトタブでコマ・吹き出し・テキスト・レイヤーパネル(グループ/テキスト/マスクレイヤーオーバーレイ、全て名前表示含め正常)の描画を確認。テンプレート一覧タブの描画(06b-template-manager.js)も確認

Why: 前段のモジュール分割に続き、ユーザーが「はい、御願いします」で追加のクリーンアップとさらなる分割を承認。バックアップが既にある前提で作業。


2026-07-10(ヘルプ「スクリプト」項目を要素タブ・列名変更に合わせて更新)

  • 「概要」: 階層構造の説明に「要素」を追加、コマワリ列名を「人物」→「要素」、「セリフ」→「セリフ/説明等」に更新
  • 「プロット(ページ > コマワリ)」: 列名変更を反映し、「要素」列が要素タブ登録名を候補にした入力可能ドロップダウンである旨を追記
  • 「プレビュー 横/縦」: 表示形式の説明を「人物:セリフ」→「要素:セリフ/説明等」に更新
  • 新規セクション「要素」を末尾に追加(登録方法・削除ボタン・プロット列との連携・「削除してもプロット既入力値は残る」仕様を明記)
  • レイアウトタブ「テキスト」項目のInsertボタン説明(「シーン/人物/セリフ」表記)は、要素機能追加時の一括置換で既に「シーン/要素/セリフ・説明等」に更新済みだったことを確認(今回の追加修正は不要)

動作確認

Kaptureでヘルプ「スクリプト」項目のDOMを取得し、「要素」セクションを含む全カードの内容が意図通りであることを確認。コンソールエラーはブラウザ拡張機能由来の定型メッセージのみ。

Why: ユーザー要望「ヘルプを更新してください」。


2026-07-10(プロット/要素タブの「要素」列表示幅を拡大)

プロットタブと要素タブの「要素」列見出しが同じCSSクラス(.project-panel-th-char、90px固定)を共有しており、どちらも狭くて表示しづらかった問題に対応。

  • static/css/style.css: .project-panel-th-charを8em(プロット表側)に変更。要素タブ側は新規.project-panel-th-element(12em)を追加し、両テーブルで別々の幅を持てるようにした
  • templates/index.html: 要素タブの「要素」見出し<th>のクラスをproject-panel-th-char→project-panel-th-elementに変更(プロット側<th>はmain.js内で動的生成のため無変更のままproject-panel-th-charを継続使用)
  • px固定ではなくem単位にしたことで、フォントサイズが変わっても文字数ベースの目安幅が保たれる

動作確認

Kaptureで、プロット「要素」列・要素タブ「要素」列とも以前より幅が広がり、12文字程度の入力(「主人公・太郎・十二文字テスト」)がほぼ収まることを確認。

補足: 「要素に追加後、プロットの要素に反映できない」との報告について

ユーザーから一時的に「要素タブに追加してもプロット側のドロップダウン候補に反映されない」との指摘があったが、値を消したら表示されたとのことで、事象は解消・再現せず。_scriptRenderElementsDatalist()はinput時に毎回呼ばれる実装で、ブラウザのdatalistキャッシュ的な表示遅延だった可能性がある(コード上の不具合は見当たらず、追加調査は行っていない)。再発時は要調査。

Why: ユーザー要望。


2026-07-10(スクリプトタブに「要素」サブタブを新設、プロット列名変更・入力可能ドロップダウン化)

登場人物・固有名詞などを名前+詳細設定として登録できる「要素」機能を追加。プロット表の「人物」列をこの要素一覧から選べる入力可能ドロップダウンに変更した。

データモデル

_script.data.elements = [{ name, detail }]を追加(_scriptBlankData()/_scriptNormalizeData())。作品ごとに保存され、localStorageのcccScriptCurrent/cccScriptWorksに同梱される(新規ストレージキーは不要)。

UI

  • templates/index.html: サブタブナビ「プレビュー縦」の右隣に「要素」ボタンを追加。中身は要素名(input)・詳細(textarea)・×削除ボタンの3列テーブル(#script-elements-tbody)+「+要素追加」ボタン。プロット表の「要素」列input用に空の<datalist id="script-elements-datalist">をグローバルに1つ配置
  • プロット表ヘッダー: 「人物」→「要素」、「セリフ」→「セリフ/説明等」に変更(データ上のフィールド名character/textは無変更、表示ラベルのみ)
  • プロット表の要素列input(.script-character-input)にlist="script-elements-datalist"を付与し、自由入力もできるネイティブ<input>+<datalist>方式のドロップダウンにした(カスタムUIを実装せず標準機能で対応)

JS(static/js/main.js)

  • 新規_scriptRenderElements(): 要素タブのテーブル行を描画。行内input/textareaのinputイベントで_script.data.elements[i]を更新しオートセーブ、名前変更時は_scriptRenderElementsDatalist()も呼んで候補を即時反映。×ボタンで該当要素を削除(配列からsplice)
  • 新規_scriptRenderElementsDatalist(): _script.data.elementsの名前(重複除去・空文字除外)から<datalist>のoptionsを再構築
  • _scriptApplyData()(作品の新規作成・保存・読込の共通経路)で_scriptRenderElements()・_scriptRenderElementsDatalist()を追加呼び出し。作品切替時に要素タブ・ドロップダウン候補とも追従する
  • サブタブ切替ハンドラにelements分岐を追加(_scriptRenderElements())
  • 「+要素追加」ボタンハンドラをinitProjectTab()に追加。末尾に空行を足して名前inputにフォーカス
  • 関連コメント・alert文言(「人物」→「要素」、「セリフ」→「セリフ・説明等」)も整合性のため更新(_scriptGetSelectedDialogue、insertScriptDialogueTextまわり)

設計判断

  • 要素一覧は「プロット列の入力候補を提供するだけ」の関係とした。要素タブで要素を削除しても、既にプロット表に入力済みの値(自由入力文字列として保存)は変更されない(参照ではなく文字列コピーのため、正規化・追跡は行わない)。動作確認でも削除後にプロット側の値がそのまま残ることを確認済み
  • 詳細設定欄はまず自由記述のtextareaのみとし、構造化フィールド(年齢・性別等)は導入しない(YAGNI、必要になれば拡張)

動作確認

Kaptureで、要素追加→名前・詳細入力→プロット「要素」列のdatalistに候補反映(DOM確認)→プロット側で入力→要素タブで削除してもプロット側の値は保持、の一連を確認。コンソールエラーはブラウザ拡張機能由来の定型メッセージのみで、アプリ起因のエラーなし。

Why: ユーザー要望。プロット表だけでは登場人物・固有名詞の詳細設定を書く場所がなく、表記揺れも防ぎたいという課題に対応。


2026-07-10(ヘルプタブを最新のUI変更に合わせて更新)

直近のセッションで行ったUI変更(表示名変更・Sタブ新設)と、以前から古くなっていた記述をヘルプタブ(_HELP_DATA、main.js)に反映した。

「スクリプト」項目

  • 新規セクション「アセットパネル『S』タブ」を追加(概要の直後)。保存済み作品(青枠選択)/ページ作品(オレンジ枠選択)の役割・折りたたみ操作・「2リストは独立選択のため同名が両方選択表示されうる」旨を明記
  • 「作品の保存・読み込み」: 「ドロップダウンから選択して読込」という記述を「アセットパネル『S』タブの一覧からクリックして選択し読込」に修正
  • 「作品名・あらすじ」: 廃止済みの「作品名の右のドロップダウン」の説明を削除

「はじめに」項目

  • 見出し文言を「Eagle Comic Creater について」→「ComfyUI Comic Creator について」(本文中の言及漏れ、以前の表示名変更時にheading側のみ直っていた)
  • 「画面構成」: タブバー一覧の「画像編集」を実際のタブ名「Image」に修正(imgeditタブは2026-07-06に削除済みで表記が古くなっていた)。アセットパネルの説明を「レイアウトタブ左、A/P/T/Fタブ」固定の記述から、タブごとに構成が変わる旨(レイアウト=A/P/T/F、Image=A/F/I、スクリプト=S)に更新

動作確認

Kaptureでヘルプタブの「スクリプト」「はじめに」両項目の表示内容を確認。コンソールエラーなし。

Why: ユーザー要望「ヘルプを更新してください」。


2026-07-10(Sタブ「ページ作品」の選択色を「保存済み作品」と区別)

ユーザーから「保存済み作品とページ作品、同じ名前が両方選択状態に見えるが排他選択か?」との質問。調査の結果、_script.selectedWorkName(保存済み作品、読込/削除ボタンが参照)と_script.selectedPageWorkName(ページ作品、新規作成時の名前初期値)は元々独立した状態変数で、各リスト内では排他選択(コード確認済み)だが、2つのリストは互いに独立しているため両方に同名選択が同時に起こりうる仕様通りの挙動(表示遅延によるバグではない)と回答。ただし見た目が紛らわしいとの指摘を受け、色分けで対応。

  • static/css/style.css: #script-asset-page-work-list .script-asset-item.selectedにIDスコープの上書きルールを追加し、「ページ作品」側の選択色のみ#0077ff(青)→#ff9800(オレンジ)に変更。「保存済み作品」側は青のまま
  • JS側の選択ロジックは無変更(CSSのみで対応)

動作確認

Kaptureで、保存済み作品=青枠、ページ作品=オレンジ枠と、同時選択時も視覚的に区別できることを確認。コンソールエラーなし。

Why: ユーザーからの指摘・要望。


2026-07-10(Sタブ「保存済み作品」「ページ作品」を折りたたみ可能に)

Aタブのフォルダ折りたたみ(.asset-folder/.asset-folder-icon回転/.asset-list.collapsed)と同じ見た目パターンで、Sタブの2セクションにも折りたたみを追加。

  • templates/index.html: 各セクションの見出し(.asset-panel-section-label)を、アイコン付きのクリック可能な.asset-panel-section-headerに変更(#script-asset-work-header/#script-asset-page-work-header)
  • static/css/style.css: .asset-panel-section-header.collapsedでアイコンをrotate(-90deg)、.script-asset-list.collapsedでdisplay:none
  • static/js/main.js: _script状態にworkListCollapsed/pageWorkListCollapsed(セッション中のみ保持、localStorage永続化はなし)を追加。新規_scriptInitAssetPanelSectionToggle(headerId, listId, stateKey)をヘッダーに1回だけバインド(initAssetPanelTabs()から呼び出し)。リストは_scriptRenderWorkList/_scriptRenderPageWorkListが呼ばれるたびにinnerHTMLごと再構築されるため、両関数の先頭でgrid.classList.toggle('collapsed', _script.xxxCollapsed)を毎回再適用し、再描画後も折りたたみ状態を保つようにした

動作確認

Kaptureで、両セクションのクリックで折りたたみ⇔展開、レイアウトタブへ切替後スクリプトタブに戻っても折りたたみ状態が維持されることを確認。コンソールエラーなし。

Why: ユーザー要望。


2026-07-10(スクリプトタブにアセットパネル「S」タブを新設し、作品選択用ドロップダウン2つを廃止)

Imageタブがアセットパネルの「I」タブのみを表示するのと同じパターンで、スクリプトタブにも専用の「S」タブを新設した。将来的に定型文などの管理もここに追加していく想定。

変更内容

  • templates/index.html: アセットパネルに#asset-panel-tab-script(Sボタン、初期非表示)と#asset-panel-view-script(「保存済み作品」「ページ作品」の2セクション)を追加。スクリプトタブ本体からは#script-work-select(保存済み作品ドロップダウン)と#script-page-work-select(ページタブ作品名ドロップダウン)を削除し、「読込」「削除」「新規作成」「保存」ボタンのみを本体側に残した
  • static/js/main.js:
    • switchTab(): _hideAssetPanelTabsから'project'を除去。tabId === 'project'のときA/P/T/F/Iタブを非表示にしてSタブへ強制切替、それ以外のタブでSタブがアクティブならAタブへ戻す処理を追加(Imageタブの既存パターンを踏襲)
    • initAssetPanelTabs()のクリックハンドラにtarget === 'script'分岐を追加(_scriptRenderAssetPanelLists()を呼ぶ)
    • _script状態にselectedWorkName/selectedPageWorkNameを追加(クリックでの選択状態のみを保持、確定操作は本体側ボタン)
    • _scriptRenderWorkSelect/_scriptRenderPageWorkSelect(<select>再描画)を_scriptRenderWorkList/_scriptRenderPageWorkList(クリック選択式のリスト再描画)に置き換え
    • 「読込」「削除」ボタンは_script.selectedWorkNameを参照するよう変更。「新規作成」ボタンは_script.selectedPageWorkNameを作品名初期値として使うよう変更(いずれもクリックでは選択状態を更新するだけで、確定はボタン押下時)
    • initProjectTab(): 旧focusイベントによる再描画(ページ作品ドロップダウン用)を廃止。スクリプトタブを開くたびにswitchTab()側の強制Sタブ切替経由で_scriptRenderAssetPanelLists()が自然に再実行されるため、他タブでの作品追加にも追従する
  • static/css/style.css: .script-asset-list/.script-asset-item(既存.work-group-itemと同一デザイン)、.asset-panel-section-labelを追加

設計判断(ユーザー確認済み)

  • 保存済み作品一覧のクリックは選択のみ(即読込ではない)。確定は本体側「読込」ボタン
  • ページ作品一覧のクリックも選択のみ(現状維持)。「新規作成」ボタン押下時の名前初期値としてのみ使う
  • 「新規作成」「保存」「読込」「削除」ボタンは本体側(編集エリア)に残す。アセットパネルは「選択・一覧表示」専用

動作確認

Kaptureで、Sタブの表示・保存済み作品クリック→選択ハイライト→「読込」ボタンで内容反映、ページ作品クリック→「新規作成」で名前欄に反映、レイアウトタブ切替でSタブが隠れAタブに戻る、をそれぞれ確認。コンソールエラーなし。「削除」ボタンは既存データ保護のため実クリックでの確認は行っていない(読込/新規作成と同じ参照変更パターンのためコードレビューで担保)。

Why: ユーザー要望。ImageタブのA/F/Iパターンに倣い、将来の定型文管理機能追加を見据えてスクリプト専用タブとして切り出した。


2026-07-10(レイアウトタブ アセットパネル「P」タブのサムネイル生成もキャッシュ化)

ページタブの作品一覧・グループ選択で対応した「サムネイルキャッシュ化」(本ファイル直前のエントリ参照)と同じ非効率パターンが、レイアウトタブ左のアセットパネル「P」(ページ)タブにも存在していたため、同じ方式で対応した。

問題

renderPageThumbGrid()(main.js、アセットパネル「P」タブの中身)が、作業中作品の全ページに対しfor...ofで1件ずつdbGet('pages', name)→buildMergedSvg()→svgTextToDataUrl()を直列実行していた。呼び出し元のrenderPageSelector()は14箇所から呼ばれており、作品を開く・ページの追加/削除/複製/並べ替えのたびに走るため、ページ数の多い作品ほど重かった(ページ切替自体は_updatePageThumbGridActive()でハイライト更新のみのため対象外)。

対応

renderPageThumbGrid()のサムネイル生成部を、既存の_getOrBuildPageThumb(pageMeta, 'pages')ヘルパー(dbPut側で保存時に計算・埋め込み済みの.thumbキャッシュを読むだけ、無ければ生成して書き戻す)に置き換え。ループの引数workPagesはstate.pages(dbGetAllPagesMeta()由来、.thumb込み)から作られているため、変更は該当箇所の数行のみで済んだ。

動作確認

Kaptureで、作品を開く→レイアウトタブ「P」タブでサムネイルが正しく表示されることを確認。コンソールエラーなし。

Why: ユーザーへ「ページタブと同じ改善が必要か」問われ、同一パターンと確認の上で対応。


2026-07-10(ページタブ「作品一覧」「グループ」選択時のサムネイル生成をキャッシュ化して高速化)

問題

ページタブの作品カード/グループを選択するたび、renderPageMgrGrid()がそのグループに属する全ページに対してdbGet('pages', name)(フルSVGレコード取得)→buildMergedSvg()(本体/各コマ/オーバーレイの3種SVGをDOMParserでパースし合成)→svgTextToDataUrl()を直列に実行しており、ページ数の多い作品/グループほど選択のたびに顕著に重かった。作品一覧タブを開いた直後のrenderWorkList()も、全作品の1ページ目に対して同様の処理を直列実行していた。

対応(案③: 保存時に事前計算してキャッシュ)

  • dbPut(storeName, data)(main.js先頭のIndexedDB操作レイヤー)でstoreNameが'pages'または'trash'かつdata.svgContentがある場合、保存直前にbuildMergedSvg+svgTextToDataUrlでサムネイルdata URLを計算しdata.thumbに埋め込むように変更。18箇所超あるdbPut('pages', ...)呼び出し全てに手を入れず、一箇所の変更で全保存経路に自動適用されるようにした
  • dbGetAllPagesMeta()(軽量メタ専用、svgContent等の重いフィールドは除外する設計)が返すオブジェクトにthumbフィールドを追加
  • 新規ヘルパー_getOrBuildPageThumb(pageMeta, storeName)を追加。pageMeta.thumbがあればそれを返すのみ、無ければ(保存済み旧データ)フルレコードを取得してdbPutし直すことで.thumbを計算・永続化してから返す(一度だけの移行コスト、以降はキャッシュヒット)
  • renderWorkList()・renderPageMgrGrid()のサムネイル生成部を、上記ヘルパー経由でキャッシュを読むだけの処理に置き換え。選択のたびに走っていたdbGet+buildMergedSvgの直列実行が実質不要になった
  • 出力(JPEG/PNG/PDF等のエクスポート)で使われているbuildMergedSvg呼び出し(フォント埋め込み経由、embedFontsInSvgと併用)はキャッシュ対象外のまま据え置き。キャッシュされたサムネイルはプレビュー専用の簡易版であり、正式出力にそのまま使うのは不適切なため

動作確認

Kaptureで、リロード後を含め作品カード選択→ページ一覧のサムネイル表示、グループ(stock)選択→空グループの正常表示、コンソールエラーなしを確認。リロード後も初回選択でサムネイルが即座に表示されたことから、.thumbのIndexedDBへの永続化とキャッシュ読み込みが機能していることを確認済み。

Why: ユーザー要望「ページタブの作品一覧・グループの表示が重いため改善したい」。事前に処理内容を説明したところ、保存時キャッシュ方式(③)を選択。

How to apply: 今後、ページレコード(pages/trashストア)のSVG内容を変更する処理を追加する際は、その保存が既存のdbPut('pages'|'trash', record)経由であれば.thumbは自動再計算される。recordを直接IndexedDBに書き込む新しい保存経路を作る場合はdbPutラッパーを通すこと(通さないとサムネイルが古いまま/生成されないまま放置される)。


2026-07-10(表示名を「Eagle Comic Creater」→「ComfyUI Comic Creator」に変更)

概要

アプリの表示名を「Eagle Comic Creater」から「ComfyUI Comic Creator」に変更した。DEVLOG過去分の記載(本ファイル)は変更対象外。

変更箇所

  • templates/index.html: <title>、header内<h1>
  • static/js/main.js: ヘルプ「はじめに」タブの見出し({ heading: 'Eagle Comic Creater について', ... })
  • Eagle(外部画像管理アプリ)保存時のタグ文字列 'eagle-comic-creater' → 'comfyui-comic-creater'(ユーザー確認の上で新名称に統一。既存Eagleライブラリ内の過去保存分とはタグが分断される)
    • static/js/image-tab.js(2箇所)、static/js/main.js(gmicオートセーブ)、static/js/nanobanana.js(オートセーブ)

影響調査(関連カスタムノード)

  • comfyui-vrm-pose-editor(static/js/pose3d.jsから動的import連携): 参照は相手ノード側のフォルダ名によるURL(/extensions/comfyui-vrm-pose-editor/)のみで、本ノード側の名前・パスに依存していないため影響なし
  • comfyUI-particle-pixijs: 連携先をそのDEVLOG内でcomfyui-comic-createrというフォルダ名で言及しているが、今回変更したのは表示名のみでフォルダ名・NODE_CLASS_MAPPINGS・WEB_DIRECTORYは無変更のため影響なし
  • ComfyUI custom_nodes全体を横断検索したが、本ノードのURLパス(/ccc)やフォルダ名に依存する外部コードは見つからなかった

スコープ外

  • フォルダ名(comfyui-comic-creater)、エンドポイント(/ccc)、ComfyUIメニュー登録名(ComicCreater.TopBar)は元々「Eagle」を含まない技術名のため無変更
  • PLAN_polygon_pen_tool.md内のディレクトリ名eagle_comic_creater_spaへの言及は実ディレクトリ名(未変更)を指す記述のためそのまま

2026-07-09(レイアウトタブ「3Dポーズ」をcomfyui-vrm-pose-editorへの依存に全面移行)

概要

レイアウトタブ「3Dポーズ」サブタブが独自に持っていたThree.js/VRM実装(static/js/pose3d.js、ライトエディタ・ポーズライブラリなしの簡易版)を廃止し、別途インストール済みのComfyUIカスタムノード「comfyui-vrm-pose-editor」(インストール先: custom_nodes/comfyui-vrm-pose-editor、開発元は別リポジトリで管理)へ依存する薄いブリッジ構成に全面移行した。ノードの現在のフル機能(ポーズライブラリ・ライトエディタ・ミラー・Ground/BGWall/シャドウ)をレイアウトタブから直接利用できるようになった。

comfyui-vrm-pose-editor側の変更(開発元→インストール先の順に反映)

  • js/pose_editor_3d.js(ComfyUIノードUIとComfyUI非依存のコアinitPoseEditor3Dが1934行の同一ファイルに同居していた)を分割。658〜1934行目を新規js/pose_editor_core.jsに切り出しexport化
  • コアの戻り値に、SPA側が要求する6メソッド(resizeRenderer/startLoop/stopLoop/forceReload/isContextLost/hasModel。コマの実寸に合わせた動的リサイズ・非表示時のレンダリング停止用)と、第6引数onModelReadyコールバックを追加。ノード本体の見た目・動作は不変(Kaptureで回帰確認済み)

comic-creater側の変更

  • static/js/pose3d.jsを全面書き換え。/extensions/comfyui-vrm-pose-editor/{pose_editor_core.js,light_editor.js,pose_library.js}を起動時に動的import、成功後にwindow.initPoseEditor3D/window.openPoseLibrary/window.openLightEditorへ同期関数をセット。main.js側の既存「typeof window.initPoseEditor3D!=='function'なら300ms後リトライ」ロジックとそのまま噛み合うため、main.js初期化ロジックは無改造
  • レイアウトタブの3Dポーズサブタブに「💾 poses/」「📚 ライブラリ」「↔ ミラー」「💡 ライト」の4ボタンを追加、main.jsのinitPose3DTab()に配線
  • static/vendor/(three.module.js等の重複コピー、pose3d.js以外から参照なしと確認済み)は削除し、ノード側vendorに一本化

判明した誤解(vendorバージョン差分は実は誤り)

事前調査で「開発元/comic-creater側のvendorが新しくインストール先が古い」という前提を立てたが、実際に内容をdiff -bwで比較すると改行コード(CRLF/LF)の違いを除き実質完全一致(差分はOrbitControls.jsのズーム速度定数1箇所のみ)と判明し、この前提は誤りだった。vendorの上書きは行わなかった。タイムスタンプだけで新旧を判断せず内容差分を確認すること。

保存先・API

/pose_library/*・/light_library/*はComfyUIサーバーに直接ルート登録された絶対パスAPIのため無変更で到達可能。ポーズ・ライトプリセットはノードと共有保存(custom_nodes/comfyui-vrm-pose-editor/poses/・.light_library/)される仕様とした(ユーザー確認済み)。

スコープ外(今回対応しなかったもの)

  • タイマー自動キャプチャ(⏱): ノード版は「IMAGE出力の定期更新」用途でSPAのワークフロー(コマへの確定)と意味が異なるため見送り
  • アスペクト比フレーム(overlayCvs、ノードUI固有装飾)の移植
  • comfyui-vrm-pose-editorの既知の差分ファイル(js/pose_library.js, pose_library_server.py, __init__.py, pyproject.toml)の開発元⇔インストール先同期

動作確認

Kaptureで、ノード側(Capture/ポーズライブラリ/ライトエディタ/ミラー、新規エラーなし)とSPA側(コマに配置→ポーズライブラリ→ライトエディタ→ミラー→コマに確定、新規エラーなし、state.pose3d.wrapperのモーダルへの退避・復元も正常)の両方を確認済み。

副次調査: ページ読み込み時にカーソルが回転し続ける現象(3Dポーズ機能とは無関係と判明)

上記実装完了後、ユーザーから「SPAを操作中、マウスカーソル横に回転する青いサークルがほぼずっと表示される」と報告あり。調査の結果:

  • リロード直後からDOMサイズ(Kapture計測値)が 約104KB → 約59MB → 約183MB まで数秒〜十数秒かけて単調増加し、その間クリック操作がタイムアウトすることがある(=体感的なビジーカーソルと一致)
  • 検証: templates/index.htmlのpose3d.js読み込みを一時的にコメントアウトし3Dポーズ機能を完全に無効化した状態でも、全く同じ増加パターン(数値までほぼ一致)が再現した。よって今回の3Dポーズ機能の変更が原因ではないと確定。pose3d.jsはすぐに元へ戻し済み
  • 原因は、アプリ起動時にIndexedDBから全ページ・全作品データを読み込んでレンダリングする既存処理の重さと推測される(コンソールログ上「DB connected」自体は一瞬で完了するが、その後のSVG/サムネイル描画に時間がかかる)。今回の3Dポーズ動作確認で「コマに確定」を複数回実行してテスト画像データが増えたことが、データ量増加のきっかけになった可能性はあるが、根本原因は3Dポーズ機能側のコードではない
  • ユーザーの意向により、ComfyUI再起動を挟んでしばらく様子見。原因の切り分け(page_groupsデータ量とロード時間の相関、どの処理が重いか)は未着手のまま次回以降の課題として残す
  • 2026-07-10追記: ユーザーより「VSCode(Claude Code)での作業セッション自体が原因ではないか。作業終了で症状が改善するため」との指摘あり。SPA/IndexedDB側の処理ではなく、VSCode拡張機能によるリソース消費が原因である可能性が高いと判明。SPA・comic-creater側のコード起因ではないため、この観点での追加調査は不要と判断

追加対応: comfyui-vrm-pose-editor側 v0.8.0リリース(ズームモード永続化)

上記実装のフォローアップとして、comfyui-vrm-pose-editor側にも対応を実施(詳細は当該プロジェクトのDEVLOG参照)。

  • ライトエディタの「🖱 Ctrl+右ドラッグでズーム」トグルが、リロードのたびに既定値(wheel)へ戻ってしまう問題を修正。localStorage(キーvrmPoseEditor_zoomMode)に永続化し、comic-creater側・ComfyUIワークフロー上のノードの両方で設定を共有するようにした
  • トラブル: 実装検証中、インストール先のjs/pose_editor_3d.jsが原因不明のタイミングで分割前の旧バージョンに巻き戻る現象が発生(ComfyUI Managerの自動更新等が疑われるが未確定)。console.log/alertでのデバッグ出力が一切実行されないことから発覚し、ファイルの中身を直接grepして判明。開発元から再コピーして復旧
  • pyproject.tomlのバージョンを0.7.0→0.8.0に更新し、GitHub(ketle-man/comfyui-vrm-pose-editor)へコミット・push。.github/workflows/publish.ymlによりComfy Registryへ自動公開される
  • comic-creater側のヘルプタブ(_HELP_DATA「3Dポーズサブタブ」項目)を更新し、poses/保存・ポーズライブラリ・ミラー・ライトエディタ(ズームモード永続化を含む)の説明を追記。Kaptureで表示内容を確認済み

2026-07-09(テキストスタイルモーダル新設・Imageタブ「レイアウトに送る」拡張・フキダシ尻尾/リサイズハンドル修正)

概要

フォントタブの「スタイル」設定(塗り・線・袋文字・影)を、レイアウトタブ・Imageタブのテキストツールから直接呼び出せる独立モーダルとして新設した。あわせてImageタブの「レイアウトに保存」ボタンを新規ドキュメントにも対応させて「レイアウトに送る」に改称し、フキダシ機能では尻尾接合部の見た目の不具合修正、思考フキダシの泡数調整機能追加、リサイズハンドルの重大なバグ修正を行った。

テキストスタイルモーダルの新設

  • 新規ファイルstatic/js/text-style-modal.js(非moduleのIIFE)を追加し、window.openTextStyleModal({ fontFamily, previewText, initialStyle, previewSize, onApply })を公開。pixifx.js(window.pixiFxOpen)と同じ「main.js/image-tab.js双方からwindow経由で呼べる独立スクリプト」パターンを踏襲
  • 保存データはフォントタブと同じlocalStorageキーfontmgr_text_stylesを直接読み書きするため、モーダルで保存したスタイルはフォントタブの一覧にも即座に反映される(データは共通、UIロジックのみモーダル内に複製実装)
  • 適用ロジックは新規実装せず、既存のapplyStyleToSelectedText()/insertStylePlaceholderText()(レイアウトタブ)、applyFontStyleToSelection()/insertFontStylePlaceholder()(Imageタブ)をそのまま呼ぶだけにした
  • ボタン設置場所: レイアウトタブは「テキスト」サブタブに#text-style-modal-btn

モーダルの初期値反映バグ修正

  • 当初モーダルは選択中テキストの現在のスタイルを一切読み込まず常にデフォルト値から開始する作りだったため、既に線が設定されているテキストの色や幅だけ変えようとすると、線チェックボックスがOFFのまま適用され既存の線が消えてしまう不具合があった
  • main.jsに_fontMgrExtractStyleFromTextEl(textEl, svgEl)(_fontMgrApplyStyleAttrsToTextElの逆変換、SVG属性・フィルタから現在のスタイルを復元)、image-tab.jsにgetSelectedTextStyleInfo()(textPropsから現在のフォント/サイズ/スタイルを返す)を追加し、モーダルが常に選択中テキストの現在の見た目から開始するよう修正
  • あわせてpreviewSizeオプションで選択中テキストの実際のフォントサイズをプレビューに反映。巨大なフォントサイズ(実例: SVG上2057px相当)に対して線幅のデフォルト値が相対的に細すぎて「線が見えない」ように見えるケースを、プレビューで視認できるようにした

スタイルボタンの配置: Textツール→Selectツールへ方針転換

  • 当初、Imageタブで複数のテキストレイヤーがある場合に既存テキストを選び直す手段として、Textツールのオプションバーに「選択」トグルボタン(_textPickMode/_pickTextLayerAt/SelectTool.findLayerAt)を実装したが、「Selectツールが元々キャンバス上の直接クリックでレイヤーを選択できるため、Textツール側に選択手段を重複実装する必要がない」というユーザー判断で全ロールバックした
  • 代わりにSelectツールのオプションバーに#ie-select-style-btn(「スタイル」ボタン)を追加。クリック時hasSelectedTextLayer()で判定し、falseならトースト表示、trueならgetSelectedTextStyleInfo()→モーダル→applyFontStyleToSelection()という、Textツール側のスタイルボタンと同じロジックを再利用
  • 教訓: 機能追加の初期実装がユーザーの意図と違う方向性だった場合、中途半端な旧実装の残骸(未使用フラグやメソッド)を残さず全ロールバックしてから新方針を実装する方が明確

Imageタブ「レイアウトに送る」機能拡張

  • 従来の「レイアウトに保存」ボタンは、レイアウトタブの「画像タブで編集」から開いた画像(_sourceImageElが存在するケース)でのみ有効で、New/Uploadで作成した新規ドキュメントでは使えなかった
  • _saveToLayout()を拡張し、_sourceImageElが無い場合はレイアウトタブの選択中コマ/オーバーレイへ合成結果を新規画像として挿入するようにした(既存のinsertImage(base64Data, width, height)をそのまま再利用)
  • insertImage()はコマ未選択時などにalertを出すだけで例外を投げない作りだったため、呼び出し元で成否を判定できるよう戻り値(成功時true/失敗時false)を追加。既存の呼び出し元はすべて戻り値未使用のため後方互換
  • ボタン名を「レイアウトに保存」→「レイアウトに送る」に変更

フキダシ: 尻尾接合部の細い線を解消

  • フキダシの本体と尻尾は別々のSVG <path>要素として描画されており、尻尾の付け根を本体の内側へ食い込ませる量overlapが固定2pxだったため、枠線(borderWidth)が太い設定や尻尾が細い場合に、接合部の縁取りが噛み合わず隙間(細い線)が見えていた
  • generateBombPath・rect分岐・normal分岐・_h2CalcCurveHandlePosの4箇所でoverlapをMath.max(2, borderWidth + 2)に変更し、枠線の太さに応じて食い込みを確保するようにした

思考フキダシ: 泡の間隔→個数指定、本体から離すスライダー

  • 従来generateThoughtPath()は尻尾の全長に沿って弧長を積分し、gap(次の泡の半径に対する割合)を加算しながら泡を配置する方式で、泡の数は結果的に決まる値だった。「間隔」スライダー(thoughtBubbleGap)を実装したが、ユーザーから「思い通りにならない」との指摘を受け、直接「泡の数」を指定できる方式(thoughtBubbleCount、1〜15、デフォルト5)に置き換えた。弧長上に指定個数を均等配置し、先端側が最小サイズ・本体側が最大サイズになるのは維持
  • 個数を減らす(特に1個)と、本体側の泡が本体境界のちょうど上に中心が来るため本体に完全に埋もれて見えなくなる問題が判明。本体に近い(tが大きい)泡ほど尻尾方向へ半径分押し出す補正(px += r * t * cos(tailAngleRad)等)を追加し、本体の外側に完全に出るようにした
  • この押し出し量をユーザー調整可能にする「離す」スライダー(thoughtBubbleOffset、0〜200%、デフォルト100%)を追加。0%で旧来の埋もれる見た目、200%でより大きく離れる
  • ヘルプ未更新: 上記の間隔→個数・離すスライダーについては、ユーザーからヘルプ更新の指示が無かったため_HELP_DATAには反映していない

フキダシ: リサイズハンドル(e/w/s/n)のバグ修正

  • initBalloonTools()のリサイズドラッグ処理で、四隅(se/sw/ne/nw)は対角固定点との距離を/2して半径を正しく計算していたが、辺の中央4つのハンドル(e/w/s/n)だけ/2が抜けており、ドラッグを開始した瞬間から半径が本来の約2倍になり、対角(反対側の辺)が固定されずに意図しない方向へ広がって見える重大なバグがあった
  • 4箇所すべてに/2を追加。修正後の式が「ドラッグ開始時点で値が変化しない」「反対側の辺が常に固定点のまま動かない」ことを数式で検証済み(Kaptureはmousedown保持中のmousemoveを再現する手段が無く実機ドラッグ確認はできないため、ユーザー自身のブラウザ操作で最終確認)

ヘルプ更新

  • レイアウト「テキスト」の項に「スタイル」ボタンの説明、Image「ツール」の項にSelect/Text双方のスタイルボタンの説明、フォント「レイアウトタブとの連携」の項にモーダル経由の相互適用・データ共有の説明を追記
  • Image「アクションバー: 保存系ボタンの違い」の項を、新規ドキュメントでの選択中コマへの挿入にも対応した内容に更新
  • レイアウト「フキダシ」の項に、8つのリサイズハンドルが対角/対辺固定で意図しない方向に広がらない旨を追記

動作確認

テキストスタイルモーダル・Imageタブのレイアウト送信・フキダシ尻尾の見た目改善・思考フキダシの泡数調整はKaptureで実機確認済み。リサイズハンドルのバグ修正のみ、ドラッグ操作をKaptureで再現できないためユーザー自身のブラウザ操作で確認(OK判定済み)。

Imageタブ: 選択中テキストレイヤーの色/サイズ/フォント/太字/斜体/揃えが変更できない不具合を修正

  • テキストツールのオプションバー(Color/Size/Font/Bold/Italic/Align)は、選択中のテキストレイヤーがあってもそのtextPropsを一切更新せず、常に「次に新規作成するテキストのデフォルト値」(this._textTool.xxx)だけを書き換える作りだったため、既存テキストの見た目を後から変更する手段が無かった
  • image-tab.jsに_applyTextToolChangeToSelection(partialProps)を新設。選択中レイヤーがテキストであれば_saveUndo()→textPropsを部分マージ→既存の_applyTextPropsToLayer()で再構築、という流れで各コントロールのイベントハンドラから呼ぶようにした
  • オプションバーの表示値も、選択中テキストレイヤーがあればそのレイヤーの現在値、無ければツールのデフォルト値を表示するよう分岐(const p = selLayerForOpts ? selLayerForOpts.textProps : this._textTool;)。Bold/Italicのトグルは「ツールのデフォルト値を反転」する誤ったロジックだったため、選択中レイヤーがある場合はレイヤーの実際の値を反転するよう合わせて修正
  • 上記修正後も実機確認でフォントセレクトの表示値だけ変わりキャンバスの見た目が変わらない事象が発生。原因は_rerenderTextLayer()がGoogle Fontsのダウンロード完了を待たずにctx.fontで即描画していたため、フォントファイル未取得時はフォールバック書体のまま描画されていたこと(<link>タグ自体はindex.htmlに事前設置済みでも、ブラウザは実際に使われるまでファイル本体の取得を遅延する)。_ensureLayerFontLoaded(layer)を新設し、document.fonts.load()でロード状況を確認・要求し、ロード完了後に自動で_rerenderTextLayer()を再実行するようにした
  • Kaptureで実機確認: Hachi Maru Pop⇔Arial切替で字形が明確に変化すること、色・サイズ・太字トグルが選択中レイヤーへ即座に反映されることを確認済み
  • ヘルプ更新: Image「ツール」のText項に、オプションバーの変更が選択中レイヤーへ即時反映される旨とGoogle Fonts遅延ロード時の自動再描画について追記

2026-07-08(Imageタブ: フォント選択のGoogle/System/カテゴリ対応・プロジェクト保存/再編集・Close・New時レイヤー自動追加)

概要

Imageタブに4つの改善を行った。①テキストツールのフォント選択をハードコード10種類からレイアウトタブと同じ「Google Fonts / システム / カテゴリ」切替方式に変更。②レイヤー編集状態(重ね順・各レイヤーのcanvas内容・テキスト設定等)をアセットパネルの新設「I」タブへサムネイル付きで保存し、後から読み込んで編集を再開できる「Save Project」機能を新設。③New右隣に、未保存確認ダイアログ付きの「Close」ボタンを追加。④New実行時に描画用レイヤーが1枚も無く即描画できない不便を解消するため、レイヤーを自動で1枚追加するようにした。

テキストツールのフォント選択をGoogle/System/カテゴリに対応

  • image-tab/TextTool.jsのハードコードTEXT_FONTS配列(Arial/Georgia等10種)は廃止。image-tab.js側でフォントソース切替UI(Google/System/Catボタン+カテゴリ絞り込みセレクト)を新設し、テキストツールのオプションバーに追加
  • Google Fontsはmain.js(レイアウトタブ側)の_fontMgrGoogleList()をそのままwindow経由で呼び出して再利用(function宣言のためImageタブのESMモジュールからも呼べる、既存のpushHistory等と同じパターン)
  • システムフォントはwindow.queryLocalFonts()(Local Font Access API)を直接呼び出し、結果をインスタンスにキャッシュ
  • カテゴリはlocalStorage.getItem('fontmgr_favorites')を直接読む方式で実装。main.js側の_fontMgr状態オブジェクトはconst宣言でwindow経由参照できないため、DEVLOG既出の_isEagleAutoSaveGmicEnabled()と同じ「共通のlocalStorageキーを直接読む」パターンを踏襲した
  • Kaptureで実機確認済み: 3ソースそれぞれのフォント一覧取得、カテゴリ切替による絞り込み(「お気に入り」24件など)、フォント選択→テキスト描画への反映

プロジェクト保存・再編集機能(Save Project / アセットパネル「I」タブ / Open)

  • LayerManager.toJSON()(既存のUndo/Redo機構がすでに使っていた、canvas内容をPNG dataURL化してレイヤー全体をシリアライズする仕組み)をそのまま流用し、_saveProject()でファイル名入力→サムネイルPNG+レイヤーJSONのペアをサーバーへPOSTする方式で実装
  • サーバー側(py/ccc.py): 新規POST /api/ccc/save-image-project(handle_save_image_project)を追加。assets/image/<name>.png+<name>.jsonのペアで保存。_generate_assets_json()に、imageフォルダに限り同名.jsonの有無を見てprojectPathをアセットエントリへ付与する専用分岐を追加
  • アセットパネルに新設した**「I」タブ**(data-panel-tab="images")は、Imageタブがアクティブな時のみ表示され、imageフォルダの中身だけを表示する。既存の「A」タブ(全体アセット一覧)にはimageフォルダを表示しない(_renderAssetFolders()でfolders配列を分岐)。挿入ボタンは他タブと区別するため文言を最終的に「Open」に変更
  • switchTab()に、Pタブ/Tタブと同じパターンでIタブの表示制御(Imageタブ以外では非表示、非表示化時にIタブがactiveならAタブへ強制切替)を追加
  • 読み込み側はloadProjectFromUrl(url, name)を新設。loadFromSvgElement()と同じく_layerMgr = nullにしてから_initCanvases()→this._layerMgr.fromJSON(data)(fromJSONはLayerManagerのインスタンスメソッドでstaticではない点に注意)で復元。常に新規キャンバスとして開き、既存の編集内容とは混在させない
  • 既知の制約: Layer.toJSON()はcontentW/contentH(canvasのネイティブ解像度)を保存しておらず、fromJSON側はdisplayW/displayHにフォールバックする。アップスケール等でネイティブ解像度と表示サイズが異なるレイヤーを保存・復元すると解像度が表示サイズに落ちる可能性がある(今回は既存のLayerManager実装をそのまま踏襲し、この制約自体の修正はスコープ外とした)
  • Kaptureで一連の流れ(3レイヤー作成→保存→ファイルシステムでpng/json確認→新規空キャンバス→Iタブから選択してOpen→3レイヤー完全復元)を実機確認済み

Closeボタン(未保存確認ダイアログ付き)

  • New右隣に追加。レイヤーが無い(何も開いていない)状態でクリックしても何もしない
  • 標準confirm()はOK/Cancelの2択しかできないため、独自のモーダルオーバーレイ(.ie-confirm-overlay/.ie-confirm-box、image-tab.cssに新規CSS追加)を実装し、「保存して閉じる」「保存せず閉じる」「キャンセル」の3択に対応
  • 「保存して閉じる」を選ぶと_saveProject()を呼び、戻り値(成功/キャンセル/失敗をboolean化するよう_saveProject()自体も修正)がtrueのときだけ_resetToEmpty()でレイヤー・キャンバスを破棄してプレースホルダー表示に戻す。キャンセルや保存失敗時はドキュメントを閉じない
  • Kaptureで3パターン(キャンセル/保存せず閉じる/保存して閉じる、いずれもファイルシステムでの保存結果込み)を実機確認済み

New時のレイヤー自動追加

  • 従来_newCanvas()はキャンバスとLayerManagerを初期化するだけで、レイヤーが1枚も無い状態だったため、描画するには手動で「+」ボタンを押す必要があった
  • ie-add-layer-btnと同じaddLayer("draw", "Layer 1")呼び出しを_initCanvases()直後に追加し、New実行直後から描画を開始できるようにした

ヘルプ更新

_HELP_DATAに新規「Image」ヘルプ項目(概要/アクションバーの開始・終了系ボタン/保存系ボタンの違い/プロジェクトの保存と再編集/ツール一覧/レイヤーパネル)を追加し、_HELP_ORDERにも登録。「アセットパネル」項目の概要とアセットタブ(A)の説明をIタブ新設に合わせて更新し、「Imageタブ (I)」セクションを追加。

  • ハマった点: 追記した本文中の「G'MIC」がシングルクォート文字列リテラル内でエスケープされておらず、main.js全体が構文エラーで読み込めなくなった(SyntaxError: Unexpected identifier 'MIC')。既存コード(画像サブタブの項)でもG\'MICとエスケープされている前例に倣って修正。ヘルプ本文にアポストロフィを含む固有名詞(G'MIC等)を書く際は必ずエスケープすること

動作確認

各機能について、都度Kaptureまたはユーザー自身のブラウザ操作で確認済み。

2026-07-08(レイアウト: コマ内オブジェクトの一括ロック機能)

概要

レイアウトタブのレイヤーパネルで、コマ単位で内部の全オブジェクト(フキダシ・画像・テキスト・図形・グループ)を一括ロックできる機能を追加した。既存の個別オブジェクトロック(レイヤー行の🔓ボタン)とは独立した仕組みで、コマロックを解除すれば元の個別ロック状態にそのまま戻る。

コマの一括ロック機能

  • コマ行に🔓/🔒ボタンを新設(既存のマスクボタン・枠線表示切替ボタンと並び)。トグルでg[data-clip-panel="panelId"]要素にdata-panel-locked属性を付与/削除し、savePanelSvg()でそのまま永続化(枠線非表示機能と同じ「プレビューDOM+svgContentの両方を更新」パターン)
  • renderLayerPanel()内でコマループ中に_rlpPanelLockedという一時変数を更新し、各オブジェクト生成ヘルパー(makeShapeItem/makeImageItem/makeTextItem/makeDrawShapeItem/makeGroupItem)のisLocked判定に「個別ロック || コマロック」を反映。コマロック中は個別の🔒ボタンをdisabled化し「コマが一括ロック中です」とツールチップ表示
  • キャンバス上での直接クリック選択(レイヤーパネル経由ではない5箇所のmousedown/click)も、共通ヘルパー_isObjectLocked(el)(el.dataset.lockedまたはel.closest('g[data-clip-panel]')のdata-panel-lockedを判定)に統一し、ロック中は選択・移動・編集・削除ができないようにした
  • 追加で見つかった抜け穴: テキストのダブルクリック再編集(textSvgElのdblclickリスナー、openTextInputDialog()を呼ぶ経路)にロック判定が無く、コマロック中でもテキスト編集モーダルが開けてしまっていた。_isObjectLocked(textEl)チェックを追加して修正
  • Kaptureで実機確認: コマロック→配下テキストが選択不可(ロック解除コマとの対照確認込み)、キャンバス直接クリックでの選択拒否、リロード後の永続化、テキストダブルクリックでの編集モーダル拒否、を確認済み
  • ハマった点: Kaptureでの座標クリック検証中、SVG内の意図しない要素をクリックしてテキストが一時的に複製されコマが誤ロックされる事故が発生。レイヤーパネルの削除・ロック解除ボタンで手動修復し、実データを元の状態に復元した。SVG座標系はビューポート座標と一致しないため、Kaptureでのクリック検証はselector指定を優先し、座標クリックは補助的に留めること

ヘルプ更新

ヘルプ「レイアウト」→「レイヤーパネル」の項に、コマ行の🔓ボタンによる一括ロックの説明(対象範囲・個別ロックとの関係・ダブルクリック編集も無効化される旨)を追記。

動作確認

Kaptureで一通り確認済み(詳細は上記)。サーバー側の変更は無く、ブラウザリロードのみで反映される。

2026-07-08(レイアウト: ツールペイン新設・フォントお気に入り連携・複製/移動の選択不具合修正・アセット削除機能)

概要

レイアウトタブのツール切替ボタン(ドロー/フキダシ/テキスト/画像/マスク/3Dポーズ)をアセットパネル右隣の専用「ツールペイン」に移設し、不要になった「テンプレートに追加」ボタンを廃止した。テキストのフォント選択をフォント管理タブと共通のGoogle Fonts一覧・お気に入り(カテゴリ)連携に対応させ、選択オブジェクトをコマ/ページ中央へ戻す「OC」ボタンを新設。あわせて、オブジェクトを別コマへ複製・移動した際に選択状態が正しく追従しない不具合と、その根本原因だった「排他選択されない」バグを修正した。最後にアセットパネルの素材をAlt+クリックで削除できる機能をサーバーAPIごと新設した。

レイアウトタブ: ツールペイン新設・「テンプレートに追加」廃止

  • index.htmlのアセットパネル(#asset-panel)と<main>の間に<aside id="tool-panel">を新設し、.layout-tools-row内にあったサブタブ切替ボタン(.subtab-btn)をそのまま移設。ボタンのクラス・data-subtab属性は変更していないため、既存のinitSubtabs()は無改造で動作
  • switchTab()にツールペインの表示制御を追加(レイアウトタブでのみ表示)
  • ページタブの複製・グループ機能で代替可能という判断から、「テンプレートに追加」ボタン(#page-to-template-btn)とsavePageAsTemplate()を削除。副産物として、この導線が原因だったアセットパネルTタブのサムネイル不整合(renderAssetTemplateGrid()がbuildMergedSvg()を使わずコンテンツ抜きのSVGのみ表示していた問題)も解消

フォント選択: レイアウトのテキストメニューをフォント管理タブと連携

  • #font-familyの「Google Fonts」optgroupがハードコードの6件だったのを_loadGoogleFontsToSelect()でGOOGLE_FONT_FAMILIES(フォント管理タブと共通)から動的生成するよう変更。スタイルサムネイル比較用に追加した「Zen Antique」(index.htmlのGoogle Fonts linkタグにも追加)も反映される
  • 「Google」「システム」に加えて「カテゴリ」ソースを新設。カテゴリセレクトで絞り込み、☆ボタン(_toggleFontFavorite())で選択中フォントを対象カテゴリへ追加/解除できる。データはフォント管理タブの_fontMgr.favorites(localStorage fontmgr_favorites)と共通
  • ユーザー要望により「お気に入り」表記を「カテゴリ」に改称し、配色もオレンジ(#ffb74d)から赤み寄り(#ff6e40)に変更
  • 副次バグ修正: activateFontTab()が非アクティブタブに#fff、アクティブタブに#e0e0e0をインラインstyleで直接設定しており、CSS側のダークグレー配色(.font-source-tabの#3a3a3a等)を上書きしていた。インライン上書きをやめてCSSに委ねるよう修正
  • テキストの初期フォントサイズをstate.balloon.fontSize・#font-size入力欄とも150に統一(従来はstate側300・UI表示80で不一致だった)。フォント管理タブの「スタイル」サイズ・「プリセット」サイズの初期値も150に統一

OCボタン(選択オブジェクトを中央へ移動)

  • 上部メニュー「画像タブで編集」の右隣に追加。moveSelectedObjectToCenter()が画像/テキスト/フキダシ/グループ/draw-shapeそれぞれの移動方法(x/y属性、dataset.cx/cy、data-tx/ty等)に応じて中心座標へ移動し、savePanelSvg()で保存
  • コマ内オブジェクトはそのコマの中心(panel.pointsのbbox中心)、オーバーレイ配下はページ全体の中心(viewBox中心)へ移動。コマ外にドラッグして操作不能になったオブジェクトをレイヤーパネルから選択→OCで復帰させる用途

複製・移動の選択不具合、および排他選択バグの修正

  • 選択フレーム残留: duplicateSelectedObject()/moveSelectedObject()の「異なるコマへ」分岐は、DB保存→renderLayoutTab()によるDOM再構築を経由するため、複製・移動先の新しいオブジェクトが選択状態にならず操作困難だった。再構築後に新IDでライブDOMから複製先オブジェクトを再取得し、_selectClone()で選択状態・ハンドル・パネル選択UIを復元するよう修正(同一コマ内複製・オーバーレイ複製は元々_selectClone()を使っており問題なかった)
  • フキダシ座標破損: 上記修正で選択が復元されるようになったところ、フキダシ(.balloon-shape)を別コマへ移動すると形状が壊れて表示され、クリックすると消える不具合が判明。原因は_applyCenterTranslate()がフキダシを「その他の図形」向けの汎用ロジック(transform="translate(dx,dy)"を付与するだけ)で扱っており、フキダシがdataset.cx/dataset.cyからd属性を都度再構築する仕組みと矛盾していたため(transformで見た目だけ動かしてもcx/cyは複製元のままで、後続の_updateH2ShapePath()呼び出しで位置が巻き戻り、新パネルのclip-path外に出て消えていた)。_applyCenterTranslate()にフキダシ専用分岐を追加し、dataset.cx/cy自体を移動して_updateH2ShapePath()で再構築するよう修正(ドラッグ移動と同じ方式に統一)
  • 排他選択されない根本原因: レイヤーパネルの行クリックによる選択は全種類のstateを正しくクリアしていたが、キャンバス上で直接クリックして選択する経路(selectImage()、フキダシmousedown、テキストmousedown)は一部のみクリアしており、他種類の選択が残留していた(特にテキストのcanvas mousedownはselectedImageEl等をまったくクリアしていなかった)。これが複製・移動時に「意図した対象と違うオブジェクトが操作される」不具合の実質的な原因でもあった。3箇所とも既存の包括的なクリア関数_clearObjectSelection()を呼ぶよう統一し、真の排他選択を実現

アセット削除機能(Alt+クリック)

  • 従来、アセットパネル(Aタブ)の素材ファイルを削除する手段が存在しなかった
  • サーバー側(py/ccc.py): handle_delete_asset()を新設し、folder/nameからASSETS_DIR配下の対象ファイルのみを_safe_pathでパストラバーサル対策しつつ削除→assets.json再生成。save-group-assetと同じパターンで明示的POSTルートとディスパッチテーブル両方に登録
  • フロントエンド: アセット項目のクリックハンドラでe.altKeyを判定し、confirm()確認後にdeleteAssetItem()が削除APIを呼び出して一覧を再描画
  • ついでに「グループをアセットとして登録」成功後の一覧更新がloadAssets(false)(静的ファイル直接fetch)だったのをloadAssets(true)(refresh-assets経由)に統一し、追加・削除双方で確実にサーバー側の最新状態を反映するようにした
  • 既知の注意点: 新設した/api/ccc/delete-assetエンドポイントはComfyUIサーバー起動時にルーティングされるため、コード変更を反映するにはサーバー再起動が必要(未反映の状態で叩くとaiohttpのHTTPNotFoundが返すプレーンテキストをJSON.parseしようとしてエラーになる)。エラー時のメッセージをその旨がわかるよう改善済み

ヘルプ更新

_HELP_DATAの「はじめに」画面構成・「レイアウト」概要に新見出し「ツールペイン」を追加。「レイアウト」テキストの項にフォント選択(Google/システム/カテゴリ)とアセット削除(Alt+クリック)を追記。「アセットパネル」フォントタブ(F)の項にスタイルサムネイルのフォント統一(Zen Antique)を追記。

動作確認

各変更について、都度ユーザーがブラウザで表示・操作して確認済み(ツールペイン表示、フォントカテゴリ切替、OCボタンでの復帰、別コマへの複製/移動後の選択状態、フキダシの位置保持、排他選択、アセット削除は要サーバー再起動の説明込みで確認中)。

過去のペンディング項目の解消状況(2026-07-07以前からの持ち越し分)

  • プリセットのグループ機能(2026-07-07から2回持ち越し): 今回実装したレイアウトのテキストメニュー「カテゴリ」機能(_fontMgr.favoritesをフォント単位で分類)で要件を満たしたと判断し、完了扱いとする。プリセット自体に個別のグループ属性を持たせる実装は行わない
  • BiRefNet背景除去(Processingタブの_procRemoveBackgroundBiRefNetスタブ、未実装のまま放置): Imageタブの「BG Remove」機能で背景除去自体は実装済みのため、完了扱いとする。_procRemoveBackgroundBiRefNetのスタブ関数自体は未使用のまま残置(削除はしていない)
  • ファイル保存構成の見直し(IndexedDB完結型を維持するかサーバー永続化を追加するかの方針決定): 引き続き未検討のままとする

2026-07-08(スクリプトタブ: プロットにシーン/人物列を追加・横書き/縦書きプレビュー・Insertのセル選択対応)

概要

スクリプトタブのプロットテーブルを「コマ番|セリフ番|セリフ」の3列から「シーン|コマ番|セリフ番|人物|セリフ」の5列に拡張し、ページの説明(シーン)とセリフの発話者(人物)を記入できるようにした。あわせてプロットを台本形式のテキストとして閲覧できる「プレビュー 横」「プレビュー縦」サブタブを新設し、レイアウトタブの「Insert」ボタンをプロット側の任意セル(シーン/人物/セリフ)を選択して挿入できるよう拡張した。

プロットのデータ構造変更(シーン・人物)

  • ページにscene(文字列)、各セリフを文字列から{character, text}オブジェクトに変更(main.jsの_scriptBlankPage/_scriptBlankDialogue)
  • _scriptNormalizeData()で旧形式(文字列セリフのみ・sceneなし)から新形式へ自動変換する後方互換処理を追加。既存の保存済み作品データもロード時に壊れず移行される
  • _scriptRenderPage()のテーブルを5列に拡張。シーン列はページ内の全セリフ行にまたがるrowspanセル(先頭行にのみ生成しtextareaで編集、変更はpage.sceneに保存)、人物列は各セリフ行ごとの1行input
  • シーンtextareaの高さ不具合: rows="2"固定のため、rowspanで縦に結合された背の高いセルの中でも上部2行分しか高さが埋まらず余白ができる問題が発生。.project-panel-td-sceneにheight: 1px(rowspanセルの実高さを子要素のheight:100%に伝える定番ハック)を指定し、.script-scene-textareaにheight:100%を設定して解消

プレビュー 横/縦サブタブ(旧・仮1/仮2プレースホルダー)

  • 「仮1」を「プレビュー 横」、「仮2」を「プレビュー縦」に改名し、それぞれ_scriptRenderPreviewH()/_scriptRenderPreviewV()で実装
  • 全ページ分のプロットを「シーン:内容」→ページ内の各コマのセリフを「人物:セリフ」の順で並べたテキストとして、ページ番号・コマ番・セリフ番を表示せずにページ横断で一括表示
  • 横はwriting-mode: horizontal-tbの通常表示、縦はwriting-mode: vertical-rlで縦書き・右から左の段組み表示(overflow-x: autoで列数が増えても横スクロール可)。DOM挿入順(1ページ目→2ページ目…)がそのままvertical-rlの右→左の読み順になることを利用し、横版と同じ生成ロジックを流用
  • サブタブ切替クリック時に毎回最新のプロット内容で再生成する読み取り専用ビュー

レイアウトタブ Insertボタン: セル単位の選択に対応

  • 従来はプロットの行選択=セリフ本文固定だったが、行クリック時にクリックされた要素(.script-scene-textarea/.script-character-input/それ以外)を判定して_script.sel.fieldに'scene'/'character'/'text'を記録するよう変更
  • _scriptGetSelectedDialogue()がfieldに応じてpage.scene/dlg.character/dlg.textのいずれかを返すよう変更。呼び出し元のinsertScriptDialogueText()は無改造のまま、シーン欄・人物欄を選択した状態でもInsertで挿入できるようになった
  • ヘルプ本文・Insertボタンのtitle属性も「セル(シーン/人物/セリフ)」の選択に対応した文言に更新

動作確認

各変更について、都度ユーザーがブラウザで表示・操作して確認済み(シーン列の高さ修正、プレビュー 横/縦の表示、Insertのセル選択挙動)。

2026-07-07(Imageタブ アセットパネル連携・レイアウトPタブの作品フィルタ・テンプレートウィザードのガイドグリッド)

概要

Imageタブ(Canvas 2Dレイヤーエディタ)にもレイアウトタブと同じアセットパネルのA/Fタブを追加し、アセットのレイヤー挿入とフォントタブのスタイル/プリセット適用・挿入を行えるようにした。あわせてレイアウトタブのアセットパネル「P」タブを作業中の作品のページのみに絞り込み、ページタブ「テンプレートを作成」ウィザードにガイドグリッド(表示ON/OFF・セル幅高さ・スナップ)を追加した。

Imageタブ: アセットパネル(A/F)連携

  • A/Fのみ表示: main.jsのswitchTab()で、Imageタブの時だけ.asset-panel-tab-btn[data-panel-tab="pages"/"templates"]を非表示にし、P/T選択中にImageタブへ移動した場合はAタブへ強制切替。アセットパネル自体を隠す_hideAssetPanelTabsからは'image'を削除。挿入ボタンの文言もタブに応じて「コマに挿入」⇔「レイヤーとして挿入」を切替
  • アセット(A)の挿入: handleInsertAsset()の先頭でアクティブタブがimageかを判定し、レイアウト用のコマ/オーバーレイ選択ロジックを完全にバイパスして既存の外部連携用公開APIwindow._ccImageTab.loadFromUrl(path, name)を呼ぶだけにした。SVGグループアセットも含め常に1枚のラスター画像としてレイヤー化する方針(Imageタブはレイアウトのような複数SVG要素の概念を持たないため)
  • フォント(F)のスタイル/プリセット: image-tab.jsにapplyFontStyleToSelection/applyFontPresetToSelection/insertFontStylePlaceholder/insertFontPresetPlaceholderを新設。レイアウトタブと同じ「テキスト選択中は適用、未選択なら新規挿入」の二役挙動を再現
  • Canvas 2Dでのスタイル再現: レイアウトタブ(SVG)は塗り・線をネイティブ属性、袋文字・影を<filter>(feMorphology/feDropShadow)で実現しているが、Imageタブはラスターtextレイヤーのため_rerenderTextLayer()を拡張しstrokeText/shadowColor等で同じ見た目を再現(①袋文字=太いstrokeTextを影なしで背面描画→②影ONで③通常線のstrokeText→④fillTextの順)。線/袋文字/影のぶんの追加余白は_textExtraPad()で計算し、_measureTextBox()でレイヤーの実寸に反映
  • 選択中テキストへの適用時の再構築: スタイル変更で余白(ひいてはレイヤー実寸)が変わるため、_applyTextPropsToLayer()で中心位置と表示倍率を保ったままレイヤーを再構築してから_rerenderTextLayer()を呼ぶ設計にした
  • 手動タイプしたテキスト(TextTool.js経由)はstrokeEnabled等がundefinedのままなので_rerenderTextLayerは従来通り塗りのみ描画され、無改造・回帰なし
  • Kaptureで動作確認: t1.png(mychain)をAタブから新規レイヤー挿入→800×600新規キャンバスでFタブのスタイル挿入(線・袋文字・影の合成表示)→同じテキストへ別スタイル適用(レイヤー数が増えずin-placeで見た目だけ変化)→プリセット適用(フォント/サイズ/色/線/影が同時反映)→レイアウトタブに戻してP/Tタブが復帰、いずれもコンソールエラーなし

レイアウトタブ: アセットパネル「P」タブを作業中の作品のページに絞り込み

  • renderPageThumbGrid()を変更し、state.pages全件ではなく_pageMgrGroups.data[state.activeWork.name](作品内のページ順序を保持する既存データ)でフィルタするように変更。作品未選択時は「作品が選択されていません。ページタブで作業中の作品を選択してください。」と案内表示
  • 作品切替(_workSetActive)時にもこの一覧が追従するよう、切替直後にrenderPageThumbGrid()を呼ぶよう変更
  • 作品へのページ追加・所属変更は引き続きページタブ(作品管理)側の機能に委ねる方針
  • Kaptureで動作確認: 作業中の作品(1ページ)でPタブを開くと、ページタブの作品管理で確認できる他の多数のページ(ストック等)は表示されず、作品内の1ページのみ表示されることを確認

ページタブ「テンプレートを作成」ウィザード: ガイドグリッド機能

  • 「ベースを作成」後の分割画面に「ガイドグリッド」チェックボックス+幅/高さの数値入力、「スナップ」チェックボックスを追加(templates/index.htmlの#tmplwiz-step-cut内)
  • _tmplWiz状態にgridEnabled/gridW/gridH/gridSnapを追加し、_tmplWizRender()の最後で_tmplWizRenderGrid()が<g pointer-events="none">配下にダッシュ線を描画(svg要素自体がmousedown/moveリスナーのため、グリッド線がクリックを奪うことはない)
  • 色調整: 当初rgba(255,255,255,0.6)(白)で試したがキャンバス背景(白〜薄い水色パネル)に対して視認性が低く、rgba(255,140,0,0.75)(オレンジ)に変更。その後ユーザーから「見えなくはないが細い」との指摘を受け、線の太さも倍増(width*0.0008→width*0.0016、最小値も1→2)
  • グリッドスナップ: 「スナップ」ON時、_tmplWizSnapPoint()が分割線の始点/終点をグリッド交点(gridW/gridHの倍数)に丸める。表示ON/OFFとは独立したフラグで、グリッド非表示のままスナップだけ有効にすることも可能。実装は座標変換の唯一の入口_tmplWizClientToSvg()の戻り値にスナップを適用する1箇所のみで、mousedown/mousemove/mouseupの3ハンドラすべてに自動適用される
  • 極端に細かいグリッド指定(セルサイズがページ幅/高さの1/300を下回る)は密集しすぎるため描画自体をスキップする防御を追加
  • ON/OFF・サイズ・スナップの設定はlocalStorage(tmplwiz_grid_settings)に永続化し、ウィザードを開き直しても引き継ぐ
  • Kaptureで動作確認: グリッド表示のON/OFF、幅/高さ変更に伴う間隔追従、スナップのトグル、いずれもコンソールエラーなし

ヘルプ更新

_HELP_DATAの「ページ — テンプレート」に「ガイドグリッド(分割画面)」見出しを新設し、上記グリッド/スナップ機能を記載。「アセットパネル」の概要・A/P/Fの各見出しを更新し、Imageタブでの表示制限(A/Fのみ)・挿入ボタンの違い(レイヤーとして挿入)・PタブがWork限定である旨・FタブのCanvas 2D再現方式を追記。

次回

プリセットのグループ機能(2026-07-07朝の実装分から持ち越し、Fタブの折りたたみ表示に対応する形でプリセット自体に任意のカテゴリ/グループを持たせる)は未着手のまま。

2026-07-07(フォントタブ: プリセット機能実装・レイアウトタブ連携)

概要

前日(2026-07-06)の「次回予定」を受け、「プリセット」(フォント+サイズ+スタイル参照)機能を実装し、レイアウトタブ・アセットパネルから呼び出せるようにした。あわせてスタイル/プリセットのUI改善(ドロップダウン化・別名保存・レイアウト再構成)と、アセットパネルへの「フォント」タブ新設まで行った。

スタイルタブ: プレビュー背景の切替

「デフォルト」「ホワイト」トグルボタンをプレビューエリア上部に追加。背景色は.fontmgr-style-preview-canvas側に持たせ、選択状態はlocalStorage(fontmgr_style_preview_bg)へ永続化。

プリセット機能の実装

  • データ構造: {id, name, fontFamily, fontSize, isVertical, styleId}(fontmgr_text_presetsにJSON配列保存)。スタイルはstyleIdで参照する2階層設計を踏襲
  • SVGへの線・袋文字・影の適用方式はSVGフィルタで実装(前日メモに残していた「2枚重ね方式 or フィルタ、要検討」を決定)。塗り・線はテキスト要素のネイティブfill/stroke属性、袋文字・影はfeMorphology(膨張)→feFlood→feComposite→feMerge→feDropShadowを1つの<filter>にまとめて適用する_fontMgrApplyStyleAttrsToTextEl()で実装。この方式により既存の単一<text>要素前提の選択・ドラッグ・回転・リサイズロジックを一切変更せずに済んだ
  • applyTextInput()を作成/編集した要素を返すよう変更(既存呼び出し元には影響なし)し、新設のinsertPresetPlaceholderText()/applyPresetToSelectedText()から利用
  • レイアウトタブ「テキスト」サブタブにプリセット選択+挿入/適用ボタンを一時追加(後日Fタブ実装により撤去、後述)

プリセットをスタイルタブへ統合

独立していた「プリセット」プレビュータブを廃止し、スタイルタブ左パネル下部(既存コントロールの余白)にプリセットのフォーム一式を統合。プレビューもスタイルタブと共通のキャンバスを再利用し、プリセットの「スタイル選択」を変更するとそのスタイルの見た目が同じキャンバスに反映されるようにした。

登録済みスタイル/プリセットのUI改善

  • チップ一覧→ドロップダウン選択+削除ボタンに変更(#style-select/#preset-select + #style-delete-btn/#preset-delete-btn)
  • 保存時、読込中のエントリから名前を変更すると別名で新規保存(元のエントリは変更しない)、名前を変えなければ上書き、という仕様に変更(従来は「新規」ボタンを押さないと必ず上書きになっていた)

レイアウトの微調整

  • .fontmgr-right(プロパティペイン)と.fontmgr-style-controlsの幅を拡大し、.btnにwhite-space:nowrapを追加して「追加/解除」「保存/新規」ボタンの文字が縦に折り返される不具合を修正
  • #fontmgr-tab.tab-content.activeにheight:100%;overflow:hiddenが無く、左パネルのカテゴリ展開でタブ全体の高さが伸縮しプレビュー中央の文字位置がずれる不具合を修正(#output-tab等と同じ既知パターンが未対応のまま残っていた)

スタイル設定の項目再構成・斜体/下線追加

  • 塗り・線を1行に統合、影を「色+ぼかし」の行と「影位置:X・Y」の行に分割
  • 斜体・下線チェックボックスを新設。プレビュー(CSS)・実SVG適用(font-style/text-decoration属性)ともに対応

アセットパネルのタブ短縮+「フォント」タブ新設

  • タブ名を「アセット」「ページ」「テンプレート」→「A」「P」「T」に短縮(title属性でフルネーム表示)
  • 「フォント」(F)タブを新設。保存済みスタイル/プリセットをサムネイル一覧表示(ミニプレビューはスタイルタブの_fontMgrRenderTextStylePreview()を共用)
  • サムネクリックの挙動: テキスト未選択時は選択中のコマ中心に新規挿入(スタイルの場合は現在のデフォルトフォントのまま挿入)、テキスト選択中はそのテキストへ適用
  • サムネ背景色パレット(デフォルト/黒/白/グレー/手動)を追加。選択色はlocalStorageへ永続化し、暗背景と同系色の線など見づらいスタイルを確認しやすくした
  • スタイル/プリセットの一覧を、フォントタブの左パネルと同じ_fontMgrGroupOpen/_fontMgrToggleGroupを再利用した折りたたみグループ表示に変更(将来のプリセットのグループ機能実装を見据えた設計)

自動更新の修正(2件)

レイアウトタブの「テキスト」サブタブのプリセットセレクト、およびFタブのサムネ一覧が、既にアクティブな状態のままフォントタブで新規保存してレイアウトタブへ戻っても更新されない不具合があったため、switchTab('layout')のタイミングで再描画するよう修正(前者は後にUI自体を撤去、Fタブ側の自動更新は存続)。

旧UIの削除

Fタブの実装により不要になった、レイアウトタブ「テキスト」サブタブのプリセットドロップダウン・挿入/適用ボタンと、関連関数_fontMgrRefreshPresetSelectInLayout()を削除。insertPresetPlaceholderText()/applyPresetToSelectedText()自体はFタブのサムネクリックから引き続き使われるため存続。

ヘルプ更新

_HELP_DATAの「フォント」「アセットパネル」項目を全面更新し、上記の新機能(スタイル/プリセット作成・別名保存、Fタブでの挿入/適用、背景パレット、A/P/T/F表記)を反映。「はじめに」の画面構成の説明にもフォント関連の導線を追記。

動作確認(Kapture実機)

スタイル/プリセットの作成・保存・別名保存・削除・ドロップダウン読込、フィルタのDOM生成(fill/stroke/paint-order/filter要素の中身)、レイアウトタブでの挿入・適用(フォント・サイズ・装飾の反映、フィルタの入れ替え)、Fタブでのサムネ表示・挿入/適用・背景パレット切替・折りたたみ・自動更新、ヘルプ表示、いずれもコンソールエラーなしで確認済み。

次回

プリセットのグループ機能(Fタブの折りたたみ表示に対応させるための実データ側の対応)を実装予定。

2026-07-06(旧「画像編集」タブ(imgedit)削除・Imageタブ機能追加)

概要

Imageタブへの機能統合が完了したことを受け、既存の「画像編集」タブ(imgedit)本体を削除。あわせてImageタブへ2件の機能追加(My CurveのmychainフォルダードSelect対応・Save to Galleryボタン)を実施。

imgeditタブ削除

  • templates/index.html: nav内data-tab="imgedit"ボタンとimgedit-tabセクション(212行)を削除
  • static/js/main.js: _imgeditState〜_imgeditGmicInsertResultのメインブロック(約1730行)、pixiFxOpenForImgedit、imgedit専用ヘルパー_editDrawBox/_editRoundedRect/_editApplyBoxBlur/_editApplyMosaic(呼び出し元が無いことを確認した上で削除)、switchTab()のimgedit分岐、_hideAssetPanelTabsの'imgedit'エントリ、ヘルプタブの「画像編集」項目(_HELP_DATA/_HELP_ORDER)を削除
  • 共用コードの扱い: _imgeditDrawOriginalUnitはレイアウトタブの「レイヤー描画」機能(_layerDrawMouseMove)からも呼ばれていたため、_layerDrawOriginalUnitにリネームしてボックス描画共通ヘルパー群のそばに移設。_loadDefaultOriginalImgはimgedit向けの初期化部分のみ除去し、レイヤードロータブ用ロジックは維持
  • トラブルと対応: ブロックをまとめて削除した際、Imageタブ連携の入口関数openImageTabWithSelected(レイアウトタブ「画像タブで編集」ボタンの遷移先)を誤って削除範囲に巻き込んでしまい、初期化処理全体がReferenceErrorで失敗する不具合が発生。image-tab.jsのloadFromSvgElement/window._ccImageTab連携を参照し、switchTab('image') + window._ccImageTab.loadFromSvgElement(imgEl)という実装で復元して解消
  • Kaptureで動作確認: 初期化エラーなし、レイアウト/Image/ヘルプ各タブの表示、「画像タブで編集」ボタンのクリックも例外なし

Imageタブ: My Curveのmychainフォルダ選択対応

ShapeツールのMy Curveで、画像をassets/mychainフォルダからドロップダウン選択できるように拡張(既存のローカルファイルアップロードは並存)。

  • image-tab.js: init()から_loadMychainAssets()を呼び、既存のアセットマニフェスト/ccc_assets/assets.json(アセットパネルと共用)からmychainフォルダのエントリを取得してキャッシュ
  • _renderToolOptions("shape")の画像選択欄に<select id="ie-shape-mychain-select">を追加。選択時にShapeTool.setOriginalImage(img, name)を呼んで反映
  • Kaptureで動作確認: ドロップダウンにt1.pngが表示され、選択後に画像名表示が更新されることを確認

Imageタブ: Save to Galleryボタン追加

アクションバーのSave PNGの右隣に「Save to Gallery」ボタンを追加。ComfyUI標準の/upload/imageエンドポイントへtype=output・subfolder=ccを指定してPOSTすることで、ComfyUI_5/output/cc/へ保存する(フォルダが存在しなくてもComfyUI本体側image_uploadが自動作成することを確認済み)。

  • image-tab.js: _saveToGallery()を追加(_uploadToComfyUI()の直前)。ファイル名はcc-image-{タイムスタンプ}.png
  • templates/index.html: #ie-save-gallery-btnをSave PNGとSave to Eagleの間に配置
  • 実機確認: output/cc/cc-image-20260706160352.pngとして保存されることをファイルシステムで確認

Imageタブ: 調整レイヤーへのマスク適用対応

調整レイヤー(明度/コントラスト/彩度/色相/ぼかし/シャープ/ノイズ/セピア/グレー化/反転/色温度/周辺減光)に、既存のマスクレイヤー機構(✂ボタンのmaskApply)でマスクを適用できるように拡張。従来は「直後の通常レイヤー1枚をクリップする」設計のみで、canvasを持たない調整レイヤー(ctxへ直接フィルタを適用する方式)はクリップ対象として非対応だった。

  • image-tab.js: _renderMaskGroupにtargetLayer.type === "adjustment"分岐を追加。適用前のctx内容を退避→フィルタ適用→マスクでdestination-in/destination-out→マスク外側をdestination-overで退避内容に復元、という非破壊マスクの標準パターンで実装
  • マスク合成部を_buildMaskCanvasに抽出して共通化
  • 保存用の_compositeForExport(旧実装は単一マスク・単一ターゲットのみでグルーピング非対応)を、プレビュー用_updateCompositeViewと同じ_computeMaskGroups+_renderLayersComposite方式に統一し、プレビューと保存結果の食い違いを解消(旧_renderMaskedLayerは不要になり削除)
  • 動作確認: 画像に grayscale 100%の調整レイヤー→直前にマスクレイヤー追加→maskApply ON(未着色)で効果が完全キャンセルされカラーに戻る/マスクツールの「Invert」でマスク全面が反転し効果が全面適用に戻ることを確認。Save to Galleryで書き出したPNG(オーバーレイなしの実合成結果)で、意図通りグレースケール全面適用されていることを確認

Imageタブ: マスクオーバーレイをアクティブレイヤー選択時のみ表示に変更

上記の検証中、マスクレイヤーが存在する限り常に赤いオーバーレイが表示され続け、他のレイヤーを選んでも消えないことが判明したため修正。

  • _renderLayersComposite: オーバーレイ表示可否を「そのマスク(グループ)がアクティブレイヤーを含むか」で個別判定するよう変更。マスクレイヤーを選択している間だけ赤色ガイドが表示され、他レイヤー選択時は実際の合成結果がそのまま見える
  • 副次的に見つかったバグを修正: Selectツールでレイヤーを切り替えた際に_updateCompositeView()が呼ばれておらず、drawCanvasの表示が更新されていなかった(オーバーレイが消えなかった主因)
  • 動作確認: マスクレイヤー選択中は赤オーバーレイ表示→画像レイヤー選択で即座に消えて実合成結果が見える→マスクレイヤーに戻すと再表示、を確認

フォントタブ: お気に入りツリーのカテゴリ折りたたみ対応

右パネルのお気に入りツリーで、カテゴリごとにフォント一覧を開閉できるように変更。

  • _fontMgrRenderFavTree: 左パネルのフォントリストと同じ_fontMgrGroupOpen/_fontMgrToggleGroupを再利用(キーはfavcat:<カテゴリ名>)。矢印クリックのみ開閉トグル、カテゴリ名クリックでの既存動作(お気に入り一覧プレビューへの切り替え)は維持
  • 対象は「お気に入りツリー内の各カテゴリ」のみ(「タグ」「お気に入り」セクション自体の開閉は対象外、ユーザー確認済み)
  • 動作確認: 矢印クリックで開閉、カテゴリ名クリックでのプレビュー切り替えは従来通り

フォントタブ: お気に入りのカテゴリ一覧を右パネルから左パネルへ統合

上記の折りたたみ対応の直後、「左パネルのソースタブ(システム/Google Fonts/お気に入り)にカテゴリタブを追加すれば右パネルの一覧は不要」という要望を受けて方針転換。右パネルのお気に入りツリー(#fontmgr-fav-tree)を削除し、左パネルにカテゴリタブとして統合した。

  • templates/index.html: 左パネルの.fontmgr-source-tabs直下に#fontmgr-fav-cat-tabsを新設(お気に入りソース選択時のみ表示)。右パネルの#fontmgr-fav-treeは削除(カテゴリ管理ボタン群は右パネルに残置)
  • main.js: _fontMgrRenderFavTreeを_fontMgrRenderFavCatTabsに置き換え、左パネルへ「すべて」+各カテゴリ名タブを描画。クリックで_fontMgr.selectedFavCatを更新し、_fontMgrCurrentList()のfavorites分岐で絞り込み。_fontMgrSwitchSource()でソース切替時にタブ表示を制御。未使用だった_fontMgr.selectedFavFamilyも削除
  • style.css: .fontmgr-fav-cat-tabs/.fontmgr-fav-cat-tab-btnを新規追加、不要になった.fontmgr-fav-tree系セレクタを削除
  • 動作確認: お気に入りソース選択時のみタブ表示、カテゴリクリックで一覧絞り込み、他ソースでは非表示になることを確認

フォントタブ: 表記統一「お気に入り」→「カテゴリ」・右パネルのレイアウト再構成

ユーザー提示のワイヤーフレーム画像に沿って、UI文言を「カテゴリ」に統一し右パネルの構成を変更(内部のsource: 'favorites'等の識別子は変更なし、表示テキストのみ)。

  • 左パネルのソースタブ「お気に入り」→「カテゴリ」、中央パネルのプレビュータブ「お気に入り一覧」→「カテゴリ一覧」、右パネル見出し「お気に入り」→「カテゴリ」
  • 右パネルを画像通りに再構成: フォント名・適用ボタンの直下に「プロパティ表示エリア」(選択中フォントのタグチップ、既存#fontmgr-tag-chipsを移動)を新設 → 区切り線 → 見出し「タグ追加」(旧「タグ」)+入力欄 → 新設: 作成済みタグ一覧(#fontmgr-all-tags-chips、全フォントの既存タグをクリックで選択中フォントへ追加できる。付与済みタグは.selectedでハイライト) → 区切り線 → 見出し「カテゴリ」(旧「お気に入り」)+管理ボタン群
  • タグの追加・削除のたびに作成済みタグ一覧も再描画し、プロパティ表示エリアとの選択状態を同期
  • 動作確認: フォント選択→プロパティエリアに既存タグ・全タグ一覧で該当タグがselected表示→未追加タグクリックでプロパティエリアに反映されることを確認

フォントタブ: 「スタイル」タブ新設(第1段階: 塗り・線・袋文字・影の作成・保存・プレビュー)

将来のプリセット機能(フォント+サイズ+スタイル参照をレイアウト/Imageタブから呼び出せるようにする)に向けた第1段階。今回は「スタイル」単体の作成・保存・プレビューのみを実装(ユーザーと段階分けに合意)。データ構造は2階層方式(スタイルは独立部品、将来のプリセットが参照する)を採用。

  • 中央パネルのプレビュータブに「スタイル」を追加。プレビューはCSSベース(既存の基本/文章/カスタムプレビューに合わせる)で、-webkit-text-strokeとtext-shadowを使用
  • 袋文字は2枚のテキストレイヤーを重ねて実現(SVG/CSSともstrokeは1色までのため): 背面レイヤーに「線幅+袋文字幅」の太い縁取りを敷き、前面レイヤーの線と重ねることで二重取りに見せる
  • データは_FONTMGR_LS_STYLES='fontmgr_text_styles'にJSON配列で保存(work_size_presetsと同じload/saveパターン)。1件は{id, name, fill, strokeEnabled/Color/Width, bukuroEnabled/Color/Width, shadowEnabled/Color/Blur/Dx/Dy}
  • チップ一覧から読込・編集・削除が可能。プレビューのフォントはフォントタブで選択中のフォントに自動追従
  • 動作確認: 線ON→袋文字ON+色変更で二重取り確認→影ONでtext-shadow値を確認→保存→リロード後も一覧に残り、クリックで全項目が復元されることを確認→削除も確認
  • 次回: 「プリセット」(フォント+サイズ+スタイル参照)の作成・保存と、レイアウト/Imageタブからの呼び出し(プレースホルダ挿入・選択テキストへの適用)を実装予定

2026-07-05(レイアウトタブ: マスクレイヤー/マスクツール)

概要

レイアウトタブにコマ単位/オーバーレイ単位のマスク機能を追加。SVG <mask>(白=表示・透明=非表示)を対象グループに適用し、ブラシ(隠す/戻す)で塗って編集する。参考: ComfyUI-Workflow-Studio Image Edit タブの MaskTool / LayerManager(maskApply・ブラシスタンプ方式)。SVG DOM基盤方針(canvasライブラリ不採用)に沿い、編集はオーバーレイcanvas+ベイクで実現。

SVG表現・データ

  • 対象: g[data-clip-panel="panel_X"](コマ)/g[data-overlay-layer](オーバーレイ、内部ID __overlay__)に mask="url(#ccc-mask-<target>)"
  • defs 内: <mask id="ccc-mask-<target>" data-ccc-mask="<target>" maskUnits="userSpaceOnUse" x/y/width/height=領域><image data-ccc-mask-img href=PNG(dataURL) preserveAspectRatio="none"/></mask>
  • マスク領域: コマ=panel.points の bbox/オーバーレイ=viewBox全体。マスクcanvasは最大辺 MASK_MAX_DIM=1400px
  • 一時無効化: mask 属性を外し g に data-ccc-mask-off="1"(def は保持)
  • 永続化: savePanelSvg が mask[data-ccc-mask=panelId] を、saveOverlaySvg が mask[data-ccc-mask="__overlay__"] を defs に持ち回るよう拡張。復元は既存 buildMergedSvg の defs マージで自動

UI

  • マスクサブタブ(画像と3Dポーズの間): 対象ラベル/編集ON・OFF/ブラシ 隠す・戻す/サイズ(画面px 4-300)・硬さ/非表示部を赤表示/マスク適用チェック(一時無効化)/反転・全表示・全非表示・マスク削除(confirm)/ステータス
  • レイヤーパネル: コマ行・オーバーレイ行に 🎭 ボタン(マスクあり=青、無効中=減光)。クリックで対象選択→マスクサブタブ→編集ON(マスク未作成なら全表示で新規作成)

実装(main.js _maskState 一式 / initMaskTool)

  • 編集ON: mask def 確保→既存 href を offscreen canvas に読込→ #image-layer にオーバーレイcanvas(layer-drawと同パターン、id _mask-edit-overlay)
  • ブラシ: radial gradient スタンプ(硬さ対応)。隠す=destination-out/戻す=白 source-over。線分補間 spacing=size*0.2。ブラシサイズは画面px→SVG単位→マスクcanvas px に換算(getScreenCTM)
  • プレビュー: オーバーレイに「非表示部分の赤表示」(赤塗り→destination-outでマスク白を抜く)+対象領域の破線枠+ブラシカーソル円。ストローク中は赤プレビューのみ更新し、pointerup で toDataURL→mask画像href更新→savePanelSvg/saveOverlaySvg(描画中の重いベイクを回避)
  • 履歴: 編集セッション(編集ON〜OFF)内の最初の操作で1回 pushHistory
  • ページ切替対策: 編集ON時に pageName を記録し、renderLayoutTab 再アタッチ時にページが変わっていたら自動で編集OFF(旧ページのマスク持ち越し防止)

動作確認(Kapture実機・複製ページ上)

🎭→マスク新規作成+編集ON(領域破線枠表示)/全非表示→コンテンツ消滅+赤表示/反転→全表示復帰/リロード後の復元(保存往復)/マスク適用OFF→一時解除、すべて確認済み。ブラシドラッグはUI経路が同一のため手動確認を推奨。

今後

画像編集タブへのマスクレイヤー・マスクツール追加を予定(workflow studio の LayerManager 型・canvasベースを想定)。

追記(同日・オブジェクト単位のレイヤーマスクに対応)

「コマ全体マスクだと最上位の塗りつぶしにしかならない」との指摘を受け、マスクを個別レイヤー(オブジェクト)に付けられるよう拡張(Photoshopのレイヤーマスク相当)。画像2枚が重なっている場合、上の画像に隠すマスクを塗るとその画像だけが消えて下のレイヤーが見える。

  • 対象解決 _maskCurrentTarget: オブジェクト(画像/フキダシ/テキスト/図形/グループ)選択中はそのオブジェクトID(IDが無ければ _maskEnsureElId で付与)、未選択時は従来のコマ/オーバーレイ全体。オブジェクト判定は「panels の id 一覧に無い target」(_maskIsObjectTarget)
  • 適用先: mask 属性を個別要素に付与。マスク領域(塗り範囲)は所属コマの bbox(オーバーレイ配下はページ全面)
  • 保存: _maskSaveFor が対象の所属(コマ/オーバーレイ)を解決して savePanelSvg/saveOverlaySvg を呼ぶ。savePanelSvg/saveOverlaySvg は「コマ/オーバーレイ全体のマスク」に加え、そのグループ内要素をtargetとする mask def も defs に持ち回るよう拡張
  • レイヤーパネル: マスク行は対象オブジェクト行の直上(インデント+1)に表示。↑↓ボタンで1つ上/下のレイヤーへ付け替え(移動先に既存マスクがある場合は alert、端では no-op)。画像行に🎭「このレイヤーにマスクを追加」ボタン追加。コマ行/オーバーレイ行の🎭は全体マスク用として存続
  • 注意: マスクは userSpaceOnUse(ページ座標)なので、対象オブジェクトを後から移動してもマスクは追従しない(workflow studio と同じ独立マスク挙動)
  • 実機確認: 重なった2画像の上側にマスク(隠す・全面塗り)→上だけ消えて下が表示✓/マスク行が対象直上に表示・↑↓動作✓/リロード後の復元(オブジェクトマスクdefの保存往復)✓

追記(同日・複数マスクレイヤー対応に改修)

ユーザー要望により単一マスク→コマ/オーバーレイごとに複数のマスクレイヤーを持てる形へ改修。

  • 構造: <mask data-ccc-mask> 内にレイヤー= <image data-ccc-mask-layer data-ccc-mask-type="hide|show" data-ccc-mask-name> を複数保持(文書順=重ね順・後勝ち)。隠すマスク(hide)=黒塗り(透明地)・表示マスク(show)=白塗り(透明地)。SVGマスクは輝度×アルファ評価なので重ね描きだけで add/subtract 合成になる
  • ベース: 可視の表示マスクが1つも無いときだけ白rect data-ccc-mask-base を最背面に自動挿入/除去(_maskSyncBase)。→ 隠すマスクのみ=全表示から減算、表示マスクあり=塗った所だけ表示
  • 旧形式移行: 旧 data-ccc-mask-img 単一画像は読取り時に表示マスクレイヤーへ自動変換(_maskLayerImgs)
  • レイヤーパネル: コマ/オーバーレイ行の配下に 🎭 マスク n(隠す/表示) 行を列挙(行クリック=そのレイヤーの編集開始・✎表示、👁=レイヤー個別の有効/無効、✕=削除)。コマ行の🎭は「レイヤー追加(種類はサブタブのセレクト)」
  • サブタブ: 種類セレクト+「+レイヤー追加」を追加。ブラシは「塗る/消す」に変更(塗り色はレイヤー種別で自動決定)。反転/クリア/全面塗り/レイヤー削除は編集中レイヤーに作用。「マスク適用」はマスク全体の一時無効化
  • 編集: 編集開始時に全レイヤーのcanvasをロードし、赤プレビューは全レイヤー合成(_maskBuildComposite: base白→show=source-over/hide=destination-out)で表示。ベイクは編集中レイヤーのみ
  • 実機確認: 隠すマスク追加→全面塗り(非表示+赤)/表示マスク追加→全面塗り(後勝ちで表示復帰)/表示マスクの👁無効化(ベース復活で再び非表示)/レイヤーパネルの2行表示・アクティブ表示、いずれも正常

2026-07-05(PixiJS FX: comfyUI-particle-pixijs 連携モーダル)

概要

カスタムノード comfyUI-particle-pixijs のパーティクル・フィルタエンジンを SPA から再利用する「PixiJS FX」モーダルを追加。レイアウトタブ「画像」サブタブと画像編集タブの両方に「✨ PixiJS FX」ボタンを設置。パーティクル4種(煙/火花/光線/スターワープ)+ pixi-filters 29種を画像にリアルタイムプレビュー付きで適用できる。

前提・アーキテクチャ

  • ComfyUI に comfyUI-particle-pixijs がインストールされていること(/extensions/comfyUI-particle-pixijs/ から同一オリジンで JS 配信される)
  • カスタムノード側をリファクタし、ComfyUI 非依存の particle_engine.js(パーティクルシステム+makeFilterInstance フィルタファクトリ)を新設。SPA は dynamic import で particle_engine.js と filter_library.js(フィルタ/テクスチャ/モーション設定モーダル)をそのまま再利用する
  • Python 側 API は不使用(キャプチャは canvas.toDataURL でクライアント完結)。ComfyUI 再起動不要

新規ファイル・変更点

  • static/js/pixifx.js(新規): モーダル本体。公開APIは window.pixiFxOpen({ imageDataUrl, onApply })
    • PIXI構成はノードと同じ: filterWrapper(bgSprite=対象画像を最背面に配置)→ scene(中心原点・scale.y=-1)→ particleLayer
    • コントロール: タイプ/数/サイズ/カラーランプ(HTML自作グラデーションエディタ)/発生点(オーバーレイcanvasでクリック追加・ドラッグ移動・矢印で方向強さ)/再生・一時停止/全面散布/ブレンド/フィルタON・OFF/画像にも適用(BG+Filter相当・デフォルトON)/背景画像非表示(パーティクルのみ透過PNG出力用)
    • 「🎬 フィルタ/詳細設定」でカスタムノードの openFilterLibrary モーダルをそのまま起動(z-index 99999 > 本モーダル 20000)
    • 出力サイズ=画像実寸(最大辺2048にクランプ)。適用で現フレームをPNGキャプチャして onApply へ
    • 設定は localStorage cccPixiFxSettings に保存・復元(テクスチャ画像は容量のため対象外。発生点は画像サイズ正規化で保存)
  • templates/index.html: レイアウト「画像」サブタブ(G'MIC隣)に #pixifx-open-btn、画像編集ツールバー(G'MIC隣)に #imgedit-pixifx-open-btn、pixifx.js の script タグ追加
  • static/js/main.js: initPixiFxButtons() / pixiFxOpenForLayout()(state.selectedImageEl → 結果を insertImage でコマに挿入。G'MICと同パターン)/ pixiFxOpenForImgedit()(対象imagelayerまたは基底画像 → G'MIC「結果を反映」と同じ反映ロジック)

カスタムノード側の変更(comfyUI-particle-pixijs、別リポジトリ)

  • web/particle_engine.js 新設(particle_widget.js からエンジン部を抽出・export化)
  • web/i18n.js の import { app } を除去(window.comfyAPI 参照+navigator フォールバックに変更)→ SPA から import 可能に
  • web/particle_widget.js はエンジンを import する形にスリム化(挙動不変)
  • 開発リポジトリ→ StabilityMatrix の custom_nodes へ web/*.js を手動コピーで同期(こちらは Junction ではなく実体ディレクトリなので注意)

動作確認(Kapture実機)

  • レイアウト: 画像選択→PixiJS FX→OldFilm選択(リアルタイムプレビュー)→適用→オーバーレイに挿入 ✓
  • 画像編集: PixiJS FX→前回設定復元→適用→キャンバス反映 ✓
  • ComfyUI 本体: リロード後 particle_widget/engine/pixi ライブラリのロード正常(ノード登録フロー実行確認)✓

追記(同日・モーダル統合)

PixiJS FX 独自モーダルを廃止し、フィルタライブラリモーダル(filter_library.js)に統合。ボタン押下で直接統合モーダルが開く。

  • filter_library.js(カスタムノード側)に後方互換フック topBar / previewElement / saveLabel / onClose を追加(ノード側動作は無変更)
  • pixifx.js: 3ペイン上部の topBar 3段にパーティクル操作を配置(1段目: タイプ/数/サイズ/再生・一時停止/全面散布/ブレンド、2段目: カラーランプ/発生点 削除・リセット、3段目: フィルタON/OFF・画像にも適用・背景画像表示・出力サイズ)。中央ペインは previewElement でライブ pixi キャンバス+発生点操作オーバーレイに差し替え(コピー描画ループ廃止)
  • フッター「✓ 適用して反映」で現フレームをキャプチャ→onClose(true) 後に onApply 実行。キャンセル/Esc/✕は全体破棄
  • 適用→置き換え→「元に戻す」の履歴復元をデバッグログ(panelSvgContent 長比較)で検証: pushHistory スナップショット・undo 後の DB 内容とも正しく復元されることを確認済み(検証は複製ページ上で実施)

追記(同日・統合モーダルのシェイプ巻き戻りバグ修正)

統合後、Particle パネルでシェイプ等を変更(プレビュー反映)した後に topBar のスライダー(数・サイズ等)を操作すると、変更前のシェイプに戻るバグを修正。原因: 詳細設定は onParticlePreview の一時スナップショット(overrides)でプレビューし、topBar 操作は確定値で rebuildParticles() するため。統合モーダルでは両UIが同時に見えるので、onParticlePreview 受信時にスナップショットを正本の変数群(shapePreset/randomShape/charSet/textures/size/spread/motion 等)へ即時反映し、topBar のサイズスライダーも同期するよう変更(pixifx.js)。snap=null(キャンセル復元通知)は無視。実機で「シェイプ変更→数スライダー操作→シェイプ維持」を確認済み。

追記(同日・出力サイズ改善)

ユーザー報告「適用で作成された画像が元より小さくなる」への対応:

  1. 原寸レンダリング: 最大辺2048pxの固定クランプを廃止し、GPUの MAX_TEXTURE_SIZE(getMaxDim()、通常16384)までは画像原寸でレンダリング・キャプチャするよう変更(pixifx.js)
  2. レイアウト経路を「置き換え」に変更: 従来は結果を insertImage で新規挿入(コマ幅フィット/オーバーレイはページ幅40%配置)していたため見た目も小さくなっていた。適用時に選択画像の href をそのまま差し替える方式に変更(SVG上の位置・表示サイズ・ピクセルサイズすべて維持)。永続化は _imgeditSave と同じパターン(saveOverlaySvg/savePanelSvg)、pushHistory 済みで「元に戻す」可能
  3. 例外: モーダルで「背景画像: 非表示」(透過出力)にした場合のみ、パーティクルのみのオーバーレイ素材として従来どおり挿入する。onApply(dataUrl, meta) の第2引数 meta.bgVisible で判別
  • 実機確認: zoomBlur 適用→選択画像が同位置・同サイズで置き換わること、「元に戻す」で復元できることを確認済み

2026-07-04(スクリプトタブ改善: ページ作品名連携・ページ送り・複数セリフ行)

概要

スクリプトタブ第2弾。①作品名入力を固定幅にし、作品管理バーの保存ボタン右隣に「ページ作品:」ドロップダウン(ページタブの作品名リスト・先頭「ー」=選択なし)を追加。選択状態で「新規作成」するとその名前が作品名にセットされる(ページ作品と同一名のスクリプト作成用)。保存済み作品セレクトは max-width:50% に制限。②プロットの複数ページ縦並びを廃止し、◀▶ページ送りで1ページ表示に変更。③コマワリを「コマ番|セリフ番|セリフ」の3列にし、1コマに複数セリフ行を持てるようにした。

UI仕様

  • 作品管理バー: 保存済み作品セレクト|読込|削除|新規作成|保存|ページ作品: ドロップダウン
  • プロットツールバー: ◀ ページ n / N ▶|コマ数 ▲▼|セリフ +−|(右寄せ)+ページ追加|ページ削除
  • セリフ+: 選択行と同一コマ番の直後にセリフ行を挿入(要行選択・挿入行が選択状態になる)。セリフ−: 選択行を削除(各コマ最低1行)
  • コマ番セルは rowspan でセリフ行数分結合

コマ枠線の個別表示/非表示トグル

  • レイヤーパネルのコマ行右端に □/− ボタンを追加(□=枠線表示中→押すと非表示、−=非表示中→押すと表示)
  • 実装: togglePanelBorderVisibility(panelId)。コマID付き枠線polygon(polygon:not(.panel-overlay):not(.panel-border) で id 一致)の inline style に stroke:none を追加/除去する。元の inline stroke 指定は data-orig-stroke に退避して復元。状態マーカーは data-border-hidden="1"
  • プレビューDOMと state.activePage.svgContent の両方を更新して dbPut('pages') で永続化(コマ枠線幅と同じパターン)。リロード後も維持されることを確認済み
  • fill はそのまま残すため、コマの白背景は維持される(消えるのは線のみ)

オブジェクト選択時のコマ選択自動追従(レイヤーパネル+キャンバス)

  • オブジェクト(フキダシ/画像/テキスト/図形/グループ)を選択すると、コマ選択がそのオブジェクトの属するコマ(オーバーレイ配下ならオーバーレイ)に自動で切り替わる。レイヤーパネルの行クリックとキャンバス上の直接クリックの両方が対象
  • 実装は syncPanelSelectionToObject(el): el.closest('g[data-clip-panel]') / g[data-overlay-layer] で所属を判定し、state.selectedPanelId/selectedOverlay 更新+ドロップダウン同期+ハイライトのみ行う(selectPanel は _clearObjectSelection を呼ぶため使用せず、オブジェクト選択・ハンドルは維持)
  • 呼び出し箇所: レイヤーパネルの make*Item クリックハンドラ4種(図形は下記経由)/キャンバス側は selectImage()・フキダシmousedown・テキストmousedown・_layerDrawSelectShape()(レイヤー/キャンバス共通)・selectGroup()

レイアウト連携: スクリプトセリフのInsertボタン

  • レイアウトタブ「テキスト」サブタブの「テキスト→PNG」右隣に Insert ボタンを追加
  • スクリプトタブのプロットでセリフ行を選択 → レイアウトでコマを選択 → Insert押下で、そのセリフを選択コマのバウンディングボックス中心にテキスト挿入(オーバーレイ/コマ未選択時はページ中心)
  • 実装は insertScriptDialogueText(): _scriptGetSelectedDialogue() でセリフ取得 → state.pendingTextPosition と #text-input-field をセットして既存 applyTextInput() を呼ぶだけ(フォント・縦書き・色・クリップ・保存の既存ロジックをそのまま再利用)
  • セリフ行未選択・空セリフ・ページ未表示時は alert でガード

実装メモ

  • データモデル変更: panels: [{ dialogue }] → panels: [{ dialogues: [''] }]。_scriptNormalizeData() で旧単数形からの変換・欠損補完(load current / 作品読込の両方で通す)
  • 状態: _script.pageIdx(表示ページ)と _script.sel = {panelIdx, dlgIdx}(選択行)。再レンダー後も選択復元
  • ページ作品名リストは _workMeta.data のキー。select の focus 時に再構築(他タブでの作品追加に追従)
  • 動作確認済み(Kapture): セリフ行追加/削除・rowspan・ページ送り・ページ追加/削除・ドロップダウン反映・新規作成での名前セット

2026-07-04(プロジェクトタブ→スクリプトタブ改編)

概要

「プロジェクト」タブを「スクリプト」に改名し、プロットサブタブを作品単位のデータ管理に作り替えた。構造は 作品名 > あらすじ > プロット[ページ > コマワリ(コマ番・セリフ)]。旧データ(プロット/あらすじ/コマワリのフラット構造)との互換なし(ユーザー合意済み・init時に旧キーを削除)。

UI仕様

  • 最上段: 作品管理バー(保存済み作品セレクト+読込/削除/新規作成/保存)
  • その下: 作品名(保存キー)→ あらすじ → サブタブ(プロット|仮1|仮2 ※仮は未実装のまま)
  • プロットサブタブ: ページブロックの縦並び。各ブロックにコマ数▲▼(最小1)・ページ削除(最後の1ページは不可)、コマワリテーブル(コマ番|セリフ)。「+ ページ追加」で末尾に追加(デフォルト4コマ)

実装メモ

  • 内部タブIDは project のまま(switchTab / help / _hideAssetPanelTabs への波及回避)。関数名 initProjectTab も維持
  • localStorage キー: cccScriptCurrent(作業中オートセーブ)/ cccScriptWorks([{name, data}])。旧 eagleComicProjectPlot / eagleComicProjectSaves は init 時に removeItem
  • データが source of truth(_script.data)。入力イベントで data 更新→保存、構造変更(ページ/コマ増減)時のみ再レンダー
  • CSSは既存 project-* クラスを再利用し、.script-page-block 等を style.css に追加
  • ヘルプ: project 項目を「スクリプト」として書き直し、「はじめに」のタブバー説明も更新
  • 動作確認済み(Kapture): 表示→入力→ページ追加→保存→リロード復元→保存済みセレクト反映、コンソールエラーなし

2026-07-03(レイアウトタブ 多角形ペンツール追加)

概要

レイアウトタブのドローツールに「多角形」を追加。クリックで頂点を積む方式(ドラッグ式の既存ツールと異なる)で、確定後は頂点単位の編集が可能。外部ライブラリなしの自作実装(Fabric.js等のcanvas系はSVGデータモデル・縦書きテキスト・出力パイプラインと相性が悪いため見送り。検討経緯は PLAN_polygon_pen_tool.md 参照)。

操作仕様

  • 頂点追加: 描画ON+形状「多角形」でクリックするたびに頂点を確定。オーバーレイcanvasに実線セグメント+カーソルへの破線ラバーバンド+塗りヒント+頂点マーカーをプレビュー表示
  • パスを閉じる: 3点以上あるとき始点付近(canvas座標で12px以内)をクリックで確定。閉じられる状態では始点マーカーが緑に強調され、ラバーバンドが始点にスナップ
  • Esc: 直前の頂点を1つ取り消し(input/textareaフォーカス中は無効)。形状切替・描画OFF・確定で描きかけは破棄
  • 確定後の編集: <polygon class="draw-shape" data-shape-kind="polygon"> として既存レイヤーシステムに統合。選択時にbboxハンドル(移動・リサイズ・回転)に加えて**頂点ハンドル(白四角・青枠)**を表示し、ドラッグで頂点を個別編集できる

実装メモ

  • 座標は points 属性に実座標で保持。移動・リサイズは _drawShapeSetBounds の polygon 分岐で全頂点をアフィン変換、回転は既存の rotate(angle,cx,cy) transform 方式
  • 回転後に頂点編集を開始すると回転をpointsに焼き込み(_polygonBakeRotation: transform除去・data-angle=0)、以降は無回転座標系で編集
  • 頂点ハンドルは draw-handle vertex-handle クラスのため、保存時のハンドル除去(savePanelSvg / saveOverlaySvg / 複製)は既存セレクタがそのまま適用される
  • Escは _layerDrawAttachOverlay / _layerDrawDetachOverlay で付け外しするwindowリスナー(_layerDrawKeyDown)
  • 動作確認済み: 頂点追加→Esc取消→始点クリック確定→頂点編集→移動→回転→焼き込み編集→IndexedDB保存・リロード復元→レイヤーパネル表示(⬠アイコン)・削除

今後の展開メモ

  • シェイプ・フキダシ・パス沿いテキスト(SVGネイティブ <textPath> を想定)は同じSVG DOM編集パターンで拡張予定

2026-07-03(レイアウトドローUI改善・レイヤーペイン常時表示・作品削除/ゴミ箱グループ)

概要

レイアウトタブのドローUIをボタン化し、レイヤーペインを常時表示に変更。ページタブ作品管理に「作品削除」ボタンと予約済み「ゴミ箱」グループ(中身確認+完全削除)を追加した。

1. レイアウトタブ ドローUIのボタン化

  • 「描画: ON/OFF」トグルを ON/OFF のセグメント式選択ボタンに変更(選択中をハイライト、ON時は緑)
  • 形状ドロップダウン(矩形/楕円/直線/曲線/鎖/ロープ/My曲線)を横並びの選択ボタンに変更。既存コードが #layer-draw-shape の値を多数参照するため、selectは非表示で残しボタンから値同期+change発火する互換方式
  • .seg-group / .seg-btn スタイルを style.css に追加(他UIでも再利用可)
  • My曲線デフォルト画像の404を修正: assets/Mychain/t1.png(SPA時代の相対パス)→ /ccc_assets/mychain/t1.png(カスタムノードのマウントポイント+実ファイルの小文字に一致)

2. レイヤーペインの常時表示化

  • 作品新規作成直後(アクティブページなし)にレイアウトタブでレイヤーペインが閉じていた問題を修正。updateTemplateSidePanel からアクティブページ有無の条件を撤廃し、レイアウトタブでは常時表示に
  • ✕(閉じる)ボタンをHTML・リスナー・CSSごと削除

3. 作品管理: 作品削除ボタンと予約済みゴミ箱グループ

  • 右ペインに「作品操作」セクションを新設(ページ操作の上)。「作品削除」ボタンは作品一覧で作品選択時のみ有効。動作はグループ削除と同等(page_groups+work_meta を削除、ページ自体は残る)。作品とグループの役割の違いを明確にする目的
  • グループタブ末尾に予約済みグループ「🗑 ゴミ箱」(内部名 TRASH_GROUP = '__trash__'、仮想グループ)を常設。選択すると trash ストアの中身を削除日時の新しい順にサムネイル表示(バッジは所属グループの代わりに削除日時)
  • ゴミ箱表示中の削除は完全削除: サイドパネルの削除ボタンはラベルが「完全削除」に変わり、確認後 dbDelete('trash', name) を実行。ツールバーの「一括削除」も同様に完全削除化。全選択もゴミ箱内を対象に動作
  • ゴミ箱表示中はページ編集系操作(名前変更・連番・複製・↑↓)を無効化。出力ボタンもゴミ箱選択時は無効
  • グループの追加・リネームで予約名(__trash__/ゴミ箱/stock)を使用禁止に

4. ページ所属の一元化(作品/stock/任意グループ)と復元機能

ページは必ず「作品・stock・任意グループ」のいずれかに属する運用に変更(無所属ページを作らない)。

  • 予約済みグループ stock: 起動時に自動作成される実グループ(STOCK_GROUP = 'stock')。リネーム・削除不可。グループタブでは 📦 アイコン付きで先頭に固定表示、ゴミ箱(🗑)は末尾
  • 復元機能: ゴミ箱表示中は「移動」ボタンが「復元」に変わり、選択ページを trash→pages に戻して移動先グループへ所属させる。同名ページが存在する場合は _restored サフィックスでリネームして復元
  • 作品削除・グループ削除は所属ページをゴミ箱へ移動(_movePageToTrashSilent を流用、confirmに移動ページ数を明示)。残したいページは事前に個別移動する運用
  • 「グループから削除」→「stockへ移動」に変更: 無所属化の代わりに stock へ退避(ゴミ箱・stock表示中は無効)
  • 「すべてのページ」ビューを廃止: 左ペイン未選択時の中央ペインは選択を促すメッセージのみ(無所属ページが存在しないため全ページビューは不要)
  • 起動時マイグレーション: 無所属ページを自動的に stock へ収容(_adoptOrphanPagesToStock)

5. 出力サブタブ: ページ削除の廃止とzip保存

  • ページ一覧の各行にあった🗑(ゴミ箱へ移動)ボタンを削除(ページ削除は作品管理サブタブに一元化)。専用関数 _movePageToTrash と関連CSSも削除
  • zip保存: 保存ボタン右隣に「zip保存」チェックボックスを追加。ON時は画像形式(PNG/JPEG/WebP)の全出力ページを1つのzipにまとめて保存(JSZip使用・読込済みCDN)。zip名は「ファイル名欄 → 作品/グループフィルタ名 → pages」の優先順。PDF/EPUBは単一ファイル出力のため対象外

6. 作品新規作成のモーダル化(サイズプリセット)

  • 作品管理ツールバーの作品名・幅・高さ入力欄を廃止し、「新規作成」ボタンでモーダル(#work-create-dialog、既存 .text-dialog スタイル流用)を開く方式に変更。「作成」で従来どおり作品を作成しレイアウトタブへ遷移
  • サイズプリセット: 標準プリセット(A4縦/A4横/B5縦/B4縦/A5縦/正方形、WORK_SIZE_PRESETS)から選択で幅・高さを自動入力。幅・高さを直接編集すると「カスタム(直接入力)」に切り替わる
  • カスタムプリセット: 「プリセット保存」で現在の幅・高さに名前を付けて保存(localStorage work_size_presets)。一覧に「★名前 (幅×高さ)」で表示され、選択中は✕ボタンで削除可能
  • _workCreate はツールバー入力参照から引数渡し (name, width, height) に変更

7. IndexedDB settingsストア未作成バグの修正

  • DB_VERSION を 3→4 に上げ、onupgradeneeded で settings ストア(keyPath: 'id')を追加。出力タブのページ並び順(output_page_order)の永続化が機能するようになった(従来は毎回 NotFoundError でリロード時に並び順がリセットされていた)
  • マルチタブでの版上げブロック対策: request.onblocked(旧バージョンを開いた他タブがあると警告アラート)と db.onversionchange(他タブの版上げ要求時に自分の接続を閉じて譲り、リロードを促す)を追加。今後のスキーマ変更でも複数タブ起動時に安全に版上げできる
  • 注意: 修正前コードで開かれたタブ(onversionchange なし)が残っていると版上げが保留される。該当タブのリロード/クローズで解消

2026-07-02(ページタブ再編・作品管理導入・レイアウトUI改善・ヘルプ全面更新)

概要

出力タブを「ページ」タブとして左端・初期表示タブに再編し、テンプレート・ページ管理を統合。「作品」(サイズ付きページグループ)の概念を導入し、作品単位のページ管理・レイアウト作業・出力フローを構築した。あわせてレイアウトタブのUI改善(ページ送り・保存・プレビューサイズ等)とヘルプタブのWFS形式への全面更新を実施。

1. タブ再編(出力タブ→ページタブ)

  • 「出力」タブを「ページ」に改名して左端に移動、起動時のデフォルトタブに変更(内部IDは output のまま維持し既存参照への波及を回避)
  • 旧「テンプレート」タブをページタブのサブタブに統合。サブタブ構成は 作品管理|テンプレート|出力(旧「ページ管理」サブタブは作品管理に統合して廃止)
  • switchTab('template') は「ページタブ+テンプレートサブタブ」へのエイリアスとして動作(ヘルプのジャンプボタン互換)。サブタブ切替は _activateOutputSubtab() に関数化
  • 初期表示タブがページタブになったため、アセットパネルはHTML側で初期 display:none(表示制御は switchTab に一元化)

2. 作品管理(3ペイン構成)

  • 作品 = サイズ情報付きページグループ。既存の page_groups(localStorage)をそのまま実体とし、サイズ等のメタは新規 work_meta(localStorage)に保存。作業中の作品は active_work で永続化・復元
  • ツールバー: 作品名+幅・高さ→「新規作成」で 作品名_年月日時分秒 のグループを作成しレイアウトへ遷移/「開く」で1ページ目をレイアウトに展開/「出力」で選択作品・グループのページのみを出力サブタブに表示
  • 左ペイン(作品一覧/グループの2タブ): 作品一覧はサムネイルカード(1ページ目・サイズ・ページ数・作業中バッジ)、グループタブは作品メタを持たない通常グループの名前リスト(ページストック用途)。クリックで中央ペインを絞り込み、再クリックで解除
  • 中央ペイン(ページ一覧): 表示順はグループ配列順=作品内ページ順。↑↓ボタンで順移動(単一選択時)。この順序がレイアウトのページ送り・「開く」の1ページ目・出力順のすべてに反映される
  • 右ペイン: プロパティ/グループ管理(作品グループのrename/deleteは work_meta・作業中状態に追従)/ページ操作(名前変更・連番名前変更・ページ複製(元名_copy、複製元直後に挿入)・移動先グループ+移動(旧「グループに追加」を置換)・グループから削除・削除)
  • 出力サブタブに作品/グループフィルタを追加(_outputFilterGroup)。フィルタバー+「解除」、フィルタ中は作品内ページ順表示・番号入力欄非表示、handleExport の出力対象・順序もフィルタに追従

3. アセットパネル「テンプレート」タブとリサイズ挿入

  • アセットパネルを「アセット/ページ/テンプレート」の3タブに拡張。テンプレートタブから「ページとして挿入」(ダブルクリック可)で新規ページを作成し、作品に登録してレイアウトに即展開
  • 作品サイズへの自動リサイズ: 背景SVGは座標数値スケーリング(_scaleSvgElementTree: polygon/rect/line/circle/ellipse/text対応、transform持ち要素とpathは scale() 前置、線幅は縦横平均倍率)、panels[].points・basePanelPoints も数値変換、コンテンツ(panelSvgContent/overlaySvgContent)は <g transform="scale()"> ラップ(_scaleSvgContentByWrap。clipPathは参照要素のユーザー座標系で解決されるためdefsはそのまま)
  • テンプレートSVG整形(text除去+panel_0枠線非表示)を _prepareTemplateSvgDocForPage() に抽出し createPageFromTemplate と共用

4. レイアウトタブUI改善

  • 保存ボタン: 現在ページをDB保存+作業中の作品へ自動登録
  • ページナビ行: ◀▶送り(作品のページ順準拠)・「n / N」番号表示・ページ削除(_movePageToTrashSilent 再利用、削除後は次ページを自動表示)。表示更新は updateLayoutPageNav() を renderLayoutTab 冒頭で呼ぶ方式
  • コマ枠線幅+「画像タブで編集」を画像操作行の右端に統合(1行削減)
  • 右サイドパネルから「コマ番号確認」を削除しレイヤーパネルを最上部に(✕ボタンはレイヤーヘッダーへ移設、updateTemplateSidePanel は表示制御のみに簡素化)
  • プレビュー表示サイズスライダー(レイヤーパネル最下部): 25〜300%・初期30%・localStorage永続化。100%以下は margin:auto で中央固定、100%超は横スクロール

5. ヘルプタブのWFS形式への全面更新

  • ComfyUI-Workflow-Studio のヘルプ形式に変更: 左サイドバーは五十音グループ折りたたみを廃止してタブ順のフラットナビ(_HELP_ORDER で順序制御、付録は区切り見出し付きで末尾)、本文はセクションごとのカード表示(.help-card)
  • 内容をタブごとに再構成・更新: 「はじめに」(基本ワークフロー)「ページ—作品管理/テンプレート/出力」「レイアウト」(ページ操作・表示サイズ追加、旧グループ機能/オーバーレイ項目を統合)「アセットパネル」を新規・書き直し。付録4件(SVG仕様・Inkscape手順)は据え置き

2026-07-02(テンプレート単一コマ分割・workflow studioタブ整理・Send CC連携・ファイル保存調査)

概要

テンプレート作成ウィザードの分割モード拡張、workflow studioタブのUI整理、Workflow Studio GalleryからComic Createrへの画像送信連携(Send CC)を実施。あわせてSPA化に伴うファイル保存方式見直しに向けた現状調査を行った(要検討・未着手)。

1. テンプレート作成ウィザード: 単一コマ分割モード追加

  • 「分割」ステップに「全体分割」(従来)/「単一コマ分割」(新規)の切替ボタンを追加(static/js/main.js の _tmplWiz.cutMode、templates/index.html にボタンとヒント文言)
  • 従来の分割ロジックは、引いた線を無限直線として扱い、その直線が交差するすべてのコマを分割していた(意図しないコマまで分割される問題)
  • 「単一コマ分割」モードでは、ドラッグ線の中点→開始点→終了点の順で点内包判定(新規 _pointInPolygon、レイキャスティング法)を行い、線を引き始めた1コマだけを分割するよう変更(_tmplWizFindPanelIndexForCut / _tmplWizCommitCut)。他のコマはその直線の延長線上にあっても変更されない

2. workflow studioタブのUI整理

  • ナビタブ名を「WSギャラリー」→「workflow studio」に変更し、.wfm-gallery-tab-btn で文字色を水色(#5ecbf2)に変更
  • 埋め込みタブ内上部にあった説明文・「再読み込み」・「新しいタブで開く」ボタンを削除
  • 削除したボタンはナビの「ヘルプ」タブ右隣に「ws再読込」「ws新しいタブで開く」として移設(新規 .ws-nav-btn)。reloadボタンのidを wfmgallery-reload-btn → ws-nav-reload-btn に変更し initWfmGalleryTab() を追従
  • 削除した説明文はヘルプタブに新規追加(_HELP_DATA に wfmgallery エントリを追加、概要・操作方法・表示されない場合の対処を記載)
  • 未使用となった .wfm-gallery-embed-toolbar / .wfm-gallery-embed-label のCSSを削除
  • ComfyUI-Workflow-Studio側のGalleryタブに「Send CC」ボタンを追加(詳細は同リポジトリの DEVLOG.md を参照)。iframe埋め込み時のみ表示され、クリックすると window.parent.insertImageFromUrl()(Comic Creater側の既存グローバル関数)を呼び出し、選択中のコマ/オーバーレイへ画像を挿入する
  • Comic Creater側の実装変更は無し。既存の insertImageFromUrl(url)(コマ・オーバーレイ判定込みで画像挿入する共通ヘルパー、main.js)をそのまま利用できた
  • 動作確認時のハマりどころ: ComfyUI-Workflow-Studioは開発リポジトリと実行時の custom_nodes/ フォルダが別実体(コピー、symlinkでない)のため、開発リポジトリ側だけ編集しても実機のGalleryタブには反映されなかった。3ファイルを実行時ディレクトリへ手動同期して解決。Comic Creater自体は custom_nodes\comfyui-comic-creater がDesktop作業フォルダへのjunctionのため、この問題は発生しない

4. ファイル保存構成の調査(要検討・未着手)

SPA化に伴うファイル保存方式の変更を検討するため、現状の保存先を4層に分けて洗い出した(実装は未着手、方針も未決定)。

  • サーバーディスク(PLUGIN_DIR 配下、py/ccc.py / py/config.py): settings.json(アプリ設定)、assets/<group>/*.svg(レイヤーパネル📦からのグループアセット登録)、output/nanobanana/*.png(Nanobanana生成画像)、output/gmic-temp/*(G'MIC GUI連携の一時ファイル)、assets/assets.json(自動生成キャッシュ)
  • IndexedDB(ComicCreatorDB, DB_VERSION=3): templates(テンプレートSVG)、pages(ページ本体。panels配列+各コマのSVG。挿入画像はbase64 dataURLとしてSVG内に直接埋め込まれ、サーバー側に別ファイルとして存在しない)、trash(削除済みページの一時保管)
  • localStorage: template_groups / page_groups / output_sort_criterion / eagle_settings / fontmgr_tags / fontmgr_favorites / fontmgr_prefs / eagleComicProjectPlot / eagleComicProjectSaves
  • File System Access API(showSaveFilePicker/showDirectoryPicker、非対応時は<a download>): 出力タブのPNG/JPEG/WebP/PDF/EPUBエクスポート(handleExport / exportToPdf / exportToEpub / _saveBlob)、3Dポーズの pose.json ダウンロード保存

未決定事項: ページ本体(画像込み)がIndexedDBに閉じ込められており、サーバー側にページ単位のファイルが一切存在しないのが最大の特徴。SPA化に伴い、この「IndexedDB完結型」構成を維持するか、サーバーディスクへの永続化やエクスポート/インポート機構を追加するかは未検討。次回作業時に方針を決定してから着手する。


2026-07-01(テンプレート作成・ページ管理機能追加)

概要

テンプレートタブ・レイアウトタブ・出力タブに、それぞれ独立した機能追加を実施。

1. テンプレート作成ウィザード(ライン分割方式)

外部ツール(Illustrator/Inkscape等)でSVGを作成してアップロードする方式に加え、SPA内でテンプレートを完結して作成できるウィザードを追加(テンプレートタブ「テンプレートを作成」ボタン)。

  • フロー: 縦長/横長選択(幅・高さは手動変更可)→ フレーム幅を指定してベース矩形を作成 → キャンバス上をドラッグして線を引くとその線でコマが分割される(Undo・リセット対応)→ 名前を付けて保存
  • 分割アルゴリズム: 半平面クリッピング(Sutherland-Hodgman方式)を新規実装(_sideOfLine / _lineIntersect / _clipPolygonByLine / _splitPolygonByLine、static/js/main.js)。既存の _insetPolygonPoints から点列パース/整形処理を _parsePointsStr / _pointsToStr として共通化
  • コマ間の隙間: 分割線の位置に「フレーム幅」と同じ幅の隙間(ガター)ができるよう、切断線を垂線方向に±(フレーム幅/2)オフセットした2本の線でそれぞれの側をクリップする方式(_offsetLinePerpendicular)
  • 保存: 生成したSVG文字列を既存の parseSVGForTemplate() / saveTemplate() にそのまま渡す設計とし、createPageFromTemplate() 等の下流処理は無改修
  • 白背景: 生成SVGにページ全面の白背景 <rect> を追加し、テンプレート一覧のサムネイルが視認しやすいようにした
  • 既存の PLAN_polygon_pen_tool.md(頂点クリックで1コマずつ多角形を描く方式)とは異なり、「線を引いて分割」というナイフツール的なUXを採用(ユーザーとの相談の上で決定)。当該計画ドキュメントは今回の実装方針とは別内容のため注意

2. ページ新規作成時のコマ線幅指定・小数点第2位対応

  • 「ページ新規作成」ボタン押下時、テンプレートに保存されている線幅をデフォルト値としたプロンプトを表示し、コマの線幅を指定できるようにした(createPageFromTemplate())
  • レイアウトタブの「コマ枠線幅」入力(#panel-border-width)も含め、線幅の値は共通ヘルパー _round2() で小数点第2位までに丸めるよう統一(step="0.01" に変更)

3. レイアウトタブのページ選択をサムネイル方式に変更

  • 左サイドバーの「アセット」パネルを「アセット/ページ」の2タブ構成に変更(.asset-panel-tabs)
  • 「ページ」タブでは各ページのサムネイル(buildMergedSvg + svgTextToDataUrl を再利用)をカード表示し、クリックで switchActivePage() を呼んで切り替え。現在開いているページには青いハイライト枠
  • 旧来の #page-select ドロップダウンはレイアウトタブから削除(テキストサブタブの #text-page-select は維持)
  • サムネイルの背景は var(--bg-primary)(ダークUIに合わせた色)とし、ページ実体(白い用紙部分)のみが白く見えるように調整
  • ページ名表示は当初 -webkit-line-clamp: 2 で2行に丸めていたが、長い名前(テンプレート名+タイムスタンプ連結で30文字超)は2行でも切り捨てられていたため、行数制限を撤廃し必要なだけ折り返して全文表示する方式に変更(.page-thumb-card-name / 出力タブの .pagemgr-card-name 両方)

4. 出力タブ「ページ管理」に連番名前変更機能を追加

  • 複数選択したページを、出力タブで事前に設定された並び順(_pageOrder)に従って「ベース名_000」「ベース名_001」…と一括リネームするボタン「連番名前変更」を追加(1件以上選択時に有効化)
  • 対象ページの中に既に ベース名_NNN と同名のページが含まれるケースでも、IndexedDBの name 主キー衝突による上書き事故を起こさないよう、一時名を経由した2段階リネームで実装
  • 既存の単一リネーム処理(renamePageInMgr)とDB更新・state追従ロジックを共通化し、_applyPageRename() として切り出し

既知の未修正バグ(要対応)

settings オブジェクトストアが IndexedDB に一度も作成されていない(DB_VERSION = 3 のまま、openDB() の onupgradeneeded に settings の createObjectStore が存在しない)。出力タブのページ並び順を dbPut('settings', {id:'output_page_order', ...}) で永続化しようとしているが、ストア自体が無いため呼び出す度にコンソールエラーになり、実際には保存されない。ページをリロードすると並び順がリセットされる。

  • 修正(DB_VERSION を4に上げて settings ストアを追加)を一度試みたが、他タブが同一IndexedDBを旧バージョンで開いていると indexedDB.open() の版上げが onblocked でブロックされ得るリスクがあり、当該セッション内では検証しきれなかったため revert 済み
  • 対応時は request.onblocked および開いている IDBDatabase の onversionchange(旧接続を自動クローズ)のハンドリングを入れた上で、マルチタブ環境での動作を確認してから反映すること

2026-07-01

概要

ComfyUI-Workflow-Studio(別カスタムノード、同一ComfyUIサーバー上で稼働)と機能が重複する部分を整理。 Workflow Studio 側でカバーされる機能(ワークフロー管理・ComfyUI連携生成・AIアシスタント・翻訳・プリセット管理・ギャラリー閲覧)を comic-creater から順次削除し、コマ割り漫画制作という本来の役割に集約した。

1. ComfyUI Outputフォルダパス設定の追加(後にGalleryタブ削除で撤去)

  • 設定タブに実体ComfyUI(例: StabilityMatrix配下)のoutputフォルダパスを直接指定できる項目を追加
  • /api/ccc/comfyui/settings GET/POST、_get_comfyui_output_dir() ヘルパー、/ccc_comfyui_output/{tail} の動的配信ハンドラを実装
  • ※ この節の機能は後続のGalleryタブ削除に伴い全て撤去済み(履歴として記録)

2. Workflow Studio ギャラリーのiframe埋め込み統合

  • 新規タブ「WSギャラリー」を追加し、<iframe src="/wfm"> でWorkflow Studio本体を埋め込み
  • 初回タブオープン時に /wfm の疎通確認 → 未インストール時はフォールバックメッセージを表示
  • 同一オリジンを利用し、iframe読み込み完了時にWorkflow Studio内の Gallery タブ(.wfm-tab[data-tab="gallery"])を自動クリックして選択
  • ヘッダー(Workflow Studioの全タブナビ)は非表示にせず、そのまま表示する方針で確定

3. プロンプトタブの削除

Workflow Studio の Prompt/AI TOOL タブと機能重複するため、関連機能を一括削除:

  • prompt-tab セクション(AIアシスタント chat・Preset Editor・Preset Manager の3カラムUI)
  • static/js/prompt_settings.js(自己完結ファイルのため丸ごと削除)
  • ComfyUI/Nanobananaタブの翻訳ボタン群・「現在のプロンプトを保存」ボタン、preset-save-dialog モーダル
  • 設定タブのOllama設定セクション、バックエンドの /api/ccc/ollama/* ルート・OLLAMA_SETTINGS
  • 波及して見つかった依存: ワークフロータブの「要約」ボタン(Ollama chat使用)、プロジェクトタブの「プロンプト」ボタンと _projectCopyToOllamaChat()

4. ComfyUI・ワークフロータブの削除

最も影響範囲が広い削除。着手前に依存関係を調査し、想定外の副作用を発見・対応した:

  • 発見した問題: レイアウトタブの「I2I」サブタブ(コマ画像→ComfyUI生成→コマ挿入)と画像編集タブのI2Iツールバーが、削除対象の comfyui.js/comfyui_editor.js に実装を依存していた(ユーザー確認の上、両方とも削除)
  • 削除ファイル: static/js/comfyui.js・comfyui_editor.js・comfyui_workflow.js(計約2760行)
  • comfyui-tab・workflow-tab のHTMLセクション(計336行)
  • main.js内のワークフローDB管理コード(wfdb系、約1034行)
  • バックエンドの /api/ccc/wfdb/* ルート群・ハンドラー・ヘルパー関数、WORKFLOWS_DIR/WORKFLOWS_DATA_DIR
  • 移植: insertImageFromUrl()(画像URLをコマに挿入する汎用処理、comfyUIオブジェクトに非依存)を comfyui.js から main.js へ移動し、Galleryタブ用に存続
  • 設定タブは「ComfyUI Output設定」(Outputフォルダパスのみ)に縮小、接続先URL・起動時デフォルトワークフローは削除

5. Galleryタブの削除

  • gallery-tab セクション(ComfyUI Output/Nanobanana/GMIC/アセットの4サブタブ)を削除
  • バックエンドの list-assets・list-comfyui-output・list-nanobanana-output・list-gmic-temp・delete-gallery-image・save-comfyui-image・serve-comfyui-output・comfyui/settings ルートとハンドラー、OUTPUT_COMFYUI_DIR(config.py)を削除
  • insertImageFromUrl() は呼び出し元が無くなったが、将来のWorkflow Studio連携(生成画像・ギャラリーからのコマ挿入機能、実装は別途予定)を見据えて関数自体は main.js に温存
  • save-nanobanana-image・OUTPUT_NANOBANANA_DIR・refresh-assets・GMIC_TEMP_DIR 本体機能は現役利用中のため維持

削除作業の進め方(今後の参考)

  • 各タブ削除の前に、他タブ・バックエンドAPIとの依存関係を必ず調査してから着手(特にComfyUI/ワークフロータブでは、削除範囲外のレイアウト/画像編集タブに波及する依存を発見)
  • 削除範囲が明確でない場合(バックエンド設定の温存可否、関連機能の扱いなど)はユーザーに確認してから実行
  • 各削除後にJS構文チェック(node --check)・Python構文チェック(ast.parse)・全ファイル横断でのID/関数名grepによる残渣確認を実施

2026-06-30

概要

SPA アプリ(eagle_comic_creater_spa)を ComfyUI カスタムノードとして統合。 http://127.0.0.1:8189/ccc で動作確認完了。

完了した作業

  • カスタムノード構成(__init__.py / py/ / templates/ / static/ / web/comfyui/)を整備
  • Windows Junction で custom_nodes/comfyui-comic-creater → 実体ディレクトリをリンク
  • ComfyUI タブ(Template / Layout / Output)の基本表示確認
  • ComfyUI 連携・Ollama 連携・基本機能の動作確認完了

解決した問題:カスタムルート 404

症状:/ccc、/api/ccc/* などすべてのカスタムルートが 404 を返す。

根本原因:aiohttp 3.14.1 の UrlDispatcher.resolve() は walk-up アルゴリズムで _resource_index を逆順に検索する。PlainResource(add_get("/ccc", ...))は 正確なパス "/ccc" にインデックスされるが、ComfyUI の web.static('/', web_root) が / 以下を傍受するため到達できないケースがある。

修正方針:PlainResource ルートは残しつつ、DynamicResource を catch-all として追加。 DynamicResource はプレフィックス前のパスにインデックスされるため walk-up で必ず到達できる。

具体的な変更(py/ccc.py):

# SPA エントリ — "/" にインデックス、walk-up で必ず到達
app.router.add_get(r"/ccc{tail:(?:/.*)?}", serve_index)

# API catch-all — "/api/ccc" にインデックス
app.router.add_get("/api/ccc/{tail:.*}", _api_get_dispatch)
app.router.add_post("/api/ccc/{tail:.*}", _api_post_dispatch)

優先順位(walk-up の特性を利用):

  • /ccc_static/... → /ccc_static で StaticResource 発見(catch-all より先)
  • /api/ccc/gmic/... → /api/ccc/gmic で既存 DynamicResource 発見(catch-all より先)
  • /ccc → PlainResource 不在 → / で catch-all DynamicResource 発見 ✓
  • /api/ccc/list-assets → PlainResource 不在 → /api/ccc で dispatch catch-all 発見 ✓

その他の変更:

  • __init__.py:sys.path 操作を廃止、相対インポート from .py.ccc import ComicCreater に変更(lora-manager パターン準拠)
  • py/ccc.py:from py.config import → from .config import(相対インポート)
  • handle_proxy_gmic:/start-server を handle_gmic_start_server へリダイレクトする特例を追加

参考にしたノード

  • comfyui-lora-manager:同じ SPA 構成で動作する参考実装(相対インポート、同一ルート登録パターン)

2026-06-30(セキュリティ修正)

セキュリティレビュー結果と対応

コードレビューで検出された 8 件の問題を全件修正。

優先度 問題 対応
Critical save-comfyui-image / save-nanobanana-image — filename パストラバーサル _safe_path() 適用
Critical save-group-asset — filename パストラバーサル _safe_path() 適用
Critical local-gmic/result_b64 — 任意ファイル読み取り GMIC_TEMP_DIR 内に限定
Critical wfdb/import — original_name パストラバーサル os.path.basename() + _safe_path() 適用
Important ollama/settings — SSRF (任意ホストへのリクエスト) _validate_local_url() でローカルホストのみ許可
Important nanobanana/key — APIキー平文露出 キー本体を返さず末尾4文字マスクのみ返す
Important eagle/add — SSRF + パス操作 _validate_local_url() + _safe_path() 適用
Important wfdb/change-thumbnail — .. チェック漏れ .. チェック追加 + _safe_path() 適用

追加した共通ヘルパー(py/ccc.py 先頭)

def _safe_path(base_dir, untrusted: str) -> Path:
    """ファイル名をサニタイズし、base_dir 外への書き込みを防ぐ。"""
    name = os.path.basename(untrusted)           # パス区切り文字を除去
    dest = (Path(base_dir) / name).resolve()     # 正規化(.. を解決)
    base = Path(base_dir).resolve()
    if not str(dest).startswith(str(base) + os.sep):
        raise ValueError(f"パストラバーサルを検出: {untrusted!r}")
    return dest

def _validate_local_url(url: str, param_name: str) -> str:
    """URL がローカルホスト(localhost / 127.0.0.1 / ::1)のみ許可する。"""
    p = urlparse(url)
    if p.scheme not in ('http', 'https'):
        raise ValueError(f"{param_name}: http/https のみ許可")
    if (p.hostname or '').lower() not in {'localhost', '127.0.0.1', '::1', '[::1]'}:
        raise ValueError(f"{param_name}: ローカルホスト以外は禁止")
    return url