# Changelog — ASA Bridge for Gemini

本拡張の version 別変更履歴。`manifest.json` の version は **release 単位で bump**、
その間の変更は同一 version 配下に蓄積する。詳細は memory `project_extension_version_policy.md` 参照。

## 1.0.5.5 (未リリース)

- 会話の読み取り部分を `conversation_parse.js` へ分離し、拡張と公式エクスポート取り込み画面が同じ処理を通るようにした（`content.js` は委譲のみ）。

## 1.0.5.4 (2026-08-14)

- PL-133 横展開: 本文も既知の画像 URL 形式も取れなかった AI の返事を捨てた時だけ、件数をログに出すようにした（普段は無出力）。network 経路の判定は変えていない。
- ストア掲載の短い説明（`extDescription`、ja/en）を書き換え。1.0.5.3 の「要デスクトップアプリ」先頭型は、検索から来た人が最初に読む 1 行が言い訳になっており、競合と並べた時に何ができる拡張か伝わっていなかった。1 文目で保存できる事、2 文目でキャプチャモードなら閉じた後の一時チャットも保存できる事（他が持たない差）を出し、単体では動かない旨は文末に残す。

## 1.0.5.3 (2026-07-09)

- ストア掲載の短い説明（`extDescription`、ja/en）を「要デスクトップアプリ」先頭型に変更。ストア一般公開に伴い、単体では動作しないことを最初に明示する。
- PL-125: コード実行で生成されたグラフ画像が保存物で表示されず、灰色の箱や URL 文字になる不具合を修正。生成画像 URL の別形（`gg-dl`）の取りこぼしもあわせて解消。
- PL-123: 共有のショッピング（新カード UI）で画像・商品名・価格が一部アカウントで丸ごとドロップする不具合を修正。あわせてリンク URL の丸括弧が本文に露出するのを解消。
- PL-121: 共有ページの保存タイトルを実際の見出し（h1）から取得し、文字化け（〼豆腐）を解消。
- PL-120: 共有ページ（`/share/`）の AI 応答・生成画像のドロップを修正。
- PL-110: 複数の画像を均一な正方タイルの格子で並べて表示するようにした（HTML / PDF 共通）。
- PL-103: ショッピング結果を全形式（HTML / md / txt / PDF）で本家相当に整備。サムネを均一なグレータイル（正方・天地中央）に揃え、長い検索リダイレクト URL の露出を解消しつつリンク機能は維持。
- PL-078: Gemini の生成画像と web 引用画像を出力に埋め込み。
- PL-085 / PL-086: web 引用画像のドロップを修正＋商品カルーセルに対応。
- PL-084: 数式を Unicode 化＋レイアウト平坦化で読みやすく描画。
- PL-088 / PL-089 / PL-090 / PL-083: web 引用画像の出典表示・配置・表示サイズ・説明キャプションを整備。
- PL-087: ショッピングカードを包むリンクの壊れ URL 露出を解消。
- PL-079: 引用（blockquote）を引用バー無しで描画し、サイト表示と一致させた。
- PL-095: ツールバーアイコンのポップアップに「保存」「設定」ボタンを追加。英語環境の保存矢印グリフを全 locale で統一。
- 対象サービス外のページでツールバーポップアップに案内表示（中央寄せ＋アイコン）。保存ボタンの文言を短縮。

## 1.0.4 (2026-05-30)

- ツールバーアイコン押下で「ご意見・不具合報告」ポップアップを追加 (`action.default_popup` = `popup.html` 新設、 従来は押下無反応)。 ボタン押下で共有 Google フォームを新規タブで開き、 対象版は事前入力 (`?usp=pp_url&entry.634899730=Gemini`) で自動セット。 新規 `popup.html` / `popup.js` + `_locales` ja/en に `popupHeading` / `popupDesc` / `popupButton` 追加。 権限追加なし (`chrome.tabs.create` は permission 不要)、 既存 content_scripts / NMH / widget 経路は touched 0。 3 版共通実装で `PRODUCT` 定数のみ各版差替 (ChatGPT/Claude/Gemini)。 フォーム本体は日本語のみ (英語需要は実観測後に追加判断)

## 1.0.3 (2026-05-25)
- PL-068 (Gemini network 経路と DOM 経路で YouTube card 引用 turn の render 結果が乖離する root を修正): root = `normalizeNetworkMarkdown` (`content.js:234-266`) が raw text 内 `googleusercontent.com/youtube_content/<N>` placeholder URL を取込むだけで、 同 turn[3] subtree に positional encoding で格納される YouTube tool 結果 structured data (= title / video_id / watch_url / channel / view_count / thumbnail URL) を walk してない設計欠陥。 capture mode (= DOM 経路) は site renderer 出力 markdown を取得するため YouTube logo + チャンネル + 視聴回数 + 大サムネ image markdown 全部入り、 通常保存 (= network 経路) は placeholder URL のみで二経路 render parity 欠落 = PL-056 (`<Image>` tag + `image_generation_content` URL normalize) と同 root family 再来。 fix = 新 helper `extractYoutubeCards(node)` 追加 (= subtree 再帰 walk で card 配列を document order で収集、 末尾 thumbnail URL `ytimg.com/vi/<id>/maxresdefault.jpg` + index 2 が `youtube.com/watch?v=<id>` の 13+ element shape で識別) + 新 helper `applyYoutubeCardReplacements(text, cards)` 追加 (= `googleusercontent.com/youtube_content/<N>` placeholder を card data 順次対応で capture mode 等価 markdown `[![画像](logo_url)\n<channel> · <views> 回の視聴](watch_url)\n\n![画像](thumbnail_url)` に置換)、 caller (`content.js:546-549`) で `normalizeNetworkText` 通過後 + `normalizeNetworkMarkdown` 通過前に挿入。 既存 PL-051 / PL-056 (= `image_generation_content` URL substitute + `<Image>` tag normalize) と別 placeholder pattern で干渉 0、 ChatGPT/Claude は別 API spec で youtube_tool 不存在で横展開不要。 Phase 0 probe (= user 提示 batchexecute response 2.2 MB raw 取得 + 構造観察) で仮説 A (= 真の image markdown / 動画 metadata 別 field 格納) 確定後の実装。 受け入れ smoke POSITIVE = 通常保存 log の YouTube 引用 turn が capture mode 同等の markdown (= logo + channel + views + thumbnail) で表示、 PL-056 root family の network ↔ DOM render parity 欠落を YouTube card UI 範囲で解消、 memory `feedback_network_dom_render_parity` 整合
- PL-062 (Gemini 新機能「ノートブック」 を ChatGPT/Claude project と同形 3 版 parity 対応): token 流用案採用 (= 既存 `include_project_*` flag を Gemini_ASA で True 化 + i18n label のみ「ノートブック/Notebook」 で override + extension content.js でノートブック名抽出)。 fix 4 段: (1) `gemini_asa/settings/dialog.py` で `include_project_subfolder=True` / `include_project_title=True` に切替、 (2) `gemini_asa/settings/title_settings.py` の `include_project=False` wrapper 撤去 + 直接 re-export 化 (= Claude precedent と同形)、 (3) `gemini_asa/settings/quick_bool_settings.py` で `_build(ctx, include_project_subfolder=True)` 渡し + `gemini_asa/settings/helpers.py` 全 rewrite で `QUICK_BOOL_KEYS` に `USE_PROJECT_SUBFOLDER` + `QUICK_TEXT_KEYS` `TITLE_PARTS` default に `project` token + `BOOL_LABEL_KEYS` 追加 (= Claude precedent と同形)、 (4) `gemini_asa/translations.py` で `_DELETIONS['en']/['ja']` から `settings.title_parts.project` + `settings.flag.use_project_subfolder` を除外 + `_OVERRIDES['en']` `Notebook` / `Create a dedicated notebook-name subfolder under each format`、 `_OVERRIDES['ja']` `ノートブック` / `ノートブック名の専用サブフォルダを作成して保存` 追加。 extension 側 fix 2 段: (5) `content.js` に `getNotebookName()` 関数追加 (`sidebar [aria-current="page"]` の `href` が `/notebook/` 開始 + `aria-label` をノートブック名として読出、 ノートブック外 chat / 未選択時 null) + `buildSnapshot` / `reportStatus` (page_status) / `dom_messages_result` / `ping_content` / `sendHeartbeat` (bridge_heartbeat) / `sendWidgetAction` (widget_action) / `tab_closed` (pagehide) / `content_ready` 計 8 emit 経路に `project: getNotebookName()` field 追加、 (6) `background.js` の 8 forward 経路 (snapshot / dom_messages / realtime snapshot / bridge_heartbeat / widget_action / content_ready / page_status / tab_closed) で `project: <source>.project || null` forwarding 追加。 DOM probe 結果 (= 2026-05-25 Session 1) で URL pattern (= `/app/<chat-id>` ノートブック内 chat / `/notebook/<uuid>` ノートブック view) + sidebar active 確証取得、 本文 noise (= 「ノートブック」 単語が message 中に出現) は属性経路抽出のため非影響。 既存 ChatGPT/Claude project 機構 (= asa_core/settings_ui/_title 汎用 `include_project` flag + extension_bridge `source.project` field) そのまま流用、 asa_core 改修 0。
- PL-058 (widget の `△ scroll_snap` icon を撤去、 代わりに `📌 toggle_topmost` icon 追加): widget HTML から scroll_snap button 削除、 同位置に dialog 最前面切替 button 追加。 click で `widget_toggle_topmost` event 送信 → asa_core `handle_widget_toggle_topmost` で `DIALOG_TOPMOST` setting toggle + `set_widget_state({dialog_topmost: <new>})` で widget 即時同期、 既存 `⦿ toggle_realtime` と同 pattern で `data-active="true"` 背景赤化により state 視認可能 (ON = 最前面固定、 OFF = 通常)。 `WIDGET_I18N` から `scroll_snap` key 削除、 `toggle_topmost` key 追加 (= ja/en)。 `applyWidgetState` 拡張 = `caps.toggle_topmost === false` で button hide + `ws.dialog_topmost` で `data-active` 同期。 scroll_snap backend (= `handle_widget_scroll_snap` + dispatcher elif + `scroll_snap_dialog.py` 等) は残置 (= 5/24 user 判定 「backend 維持」 継承、 NMH 経由 trigger 可能維持、 widget 経由のみ消失)。 既存 dialog の topmost hardcode 統一は本 PL scope 外 (= 1 PL 1 論点規範、 cleanup は別 PL 候補)
- PL-056 (reload 前/後 で出力差: AI 生 markdown source 残置 = `<Image>` tag / LaTeX inline / `image_agent_tag_*` placeholder): root = network text path (`content.js:418` `assistantTextRawSrc` 経路) に site DOM render 等価の正規化 layer 不在、 DOM path は PL-053 段 C で markdown 復元拡張済だが network path 側が `normalizeNetworkText` (cite tag 除去 + 改行整形のみ) しか通らず raw source 流出。 fix = 新 helper `normalizeNetworkMarkdown` (`content.js:222-262`) 追加 + `normalizeNetworkText` の後段に chain、 `userTextRaw` / `assistantTextRaw` 双方に対称適用 (PL-051 既存 `image_agent_tag` URL substitute は helper 内集約)。 helper 対処 3 件: (1) `<Image alt="X" src="Y"/>` tag 分岐 = src が `image_agent_tag_*` 内部 ID なら `[AI生成: X]` (= DOM path 段 C handler 等価 / PL-051 contract)、 公開 URL なら `"X"\n"Y"` 2 行 quoted (= user 明示指定形)、 (2) `googleusercontent.com/image_generation_content/<id>` URL → `[AI生成]` (= PL-051 既存 1 行 substitute を helper 集約)、 (3) LaTeX inline `$...$` → Unicode 形 (`latexInlineToUnicode` helper、 `$-2^\circ\text{C}$` → `−2∘C` 等、 観測 pattern `-` / `^\circ` / `\text{...}` 中心の限定 transform、 完全 LaTeX parser 化禁止)。 残課題 = 行頭 `* ` list の `<p><br>` 連結残置 (line 114-115 観測) は別 root 候補 (= asa_core renderer / source 改行 spec)、 本 cycle scope 外で smoke 後再判定。 受け入れ smoke = (1) reload 後保存で `<Image>` 生 tag 不在、 `"alt"\n"src"` 形 (= 公開 URL case) / `[AI生成: alt]` 形 (= 内部 ID case) 出力、 (2) reload 後保存で LaTeX `$-X^\circ\text{C}$` raw 不在、 `−X∘C` 形出力、 (3) reload 前保存 regression なし (= DOM path は変更なし)、 (4) 既存 PL-051 `[AI生成]` placeholder 維持。 ChatGPT / Claude 版同 root audit 別 cycle (= 本 PL Gemini scope 限定)
- PL-053 第 2 cycle Step 2 段 C (Codex Round 7 verdict DOM-to-markdown 復元拡張、 新規会話直後装飾喪失救済): 段 B' 実装試行 (= Codex Round 6 verdict bounded partial fallback) で新規会話 装飾喪失救えず判明、 真 root 確証取得 = `replayHistoryRequest` (= `page_hook.js`) が 「履歴 template」 経由でしか動かない構造制約で、 site が template 用意するまで active replay 自体使えない state (= 新規会話直後 + 数 turn 進行、 `before=0` + `hasHistoryTemplate=false`) では Codex Round 6 spec の partial 採用 path も `partial.length >= domMessages.length` 条件で fail (= partial 0 件)。 Codex Round 7 verdict = path C 採用 (= DOM fallback 経路の保存品質改善)、 path B (= site send/streaming endpoint 別 sniff) は 「先に debug-only probe で endpoint 観測必要、 観測なしに parser 入れると fragile patch 化」 で defer、 path A/D は主対策にせず最後の UX fallback としてのみ。 fix = `content.js` `deepText` (L1011-) の 7 種 inline / block markdown 復元拡張: (1) `h1`-`h6` を `#` `##` 等 prefix 化、 (2) `li` 親 tag (= `ol` / `ul`) 判別で ordered (= `1. ` `2. ` 連番) / unordered (= `- `) 切替、 (3) `a[href]` を `[text](href)` 化 (= href 空 / `javascript:` / href === text の場合は text のみ)、 (4) `strong` / `b` を `**text**`、 `em` / `i` を `*text*`、 (5) `code` で pre / code-block / `.code-block` 祖先 不在時のみ `` `text` `` (= 既存 marker path との二重 wrap 防止)、 (6) `blockquote` を行頭 `> ` prefix、 (7) `img[src]` を `![alt](src)` markdown image link 化 = 出力 file で画像クリック表示可能 (= user smoke 評価 「画像リンクが生きてる」 要望対応、 Codex Round 7 §Scope 直接対象外だが path C 「DOM markdown 復元品質改善」 範疇拡張、 data: / blob: は除外で巨大 base64 / 期限切れ blob 出力 file 流入防止、 `image_agent_tag_*` 等 site 内部 placeholder URL は alt 不在でも `[AI生成]` / `[画像]` text placeholder 復元で turn drop 防止 = 既存 PL-051 network 経路 `[AI生成]` contract 整合、 AI 画像生成 turn が `model-response` 子 img のみで text 0 件のとき turn 全体 drop する症状を防ぐ)。 加えて `deepText` 末尾に包括 fallback = text 0 件 + node 内 `img / picture / canvas / video` のいずれか検出時 `[AI生成]` placeholder 強制挿入で turn drop 防止 = site が `img` でない別 element (= `picture` / `canvas` / `video` / image custom element 等) で AI 生成画像を render する case を img handler 単独では救えない症状への包括対処、 純画像生成 turn (= text 0 件 + 画像系 element のみ) の完全消失を防ぐ。 既存 `tableToMarkdown` (= PL-007)、 code block marker (= PL-016 Step 2)、 attachment placeholder (= PL-017)、 a11y screen-reader 除去 (= PL-017 拡張) は維持。 nested list の完璧 indent は要求しない (= practical fallback と割り切り、 複雑 HTML renderer 化禁止)。 同時に Codex Round 6 verdict bounded partial fallback (= 段 B') を `collectConversation` の DOM fallback 直前に保持 (= 既存 chat で `template=false` + small partial が手元にある case で network partial 採用、 4 条件 guard: `partial.length >= domMessages.length` / `<= 40` / `replaySkipReason === 'no_template_partial_cache_rejected'` / `domMessages.length >= 1`、 採用時 debug 明示 = `captureSource='network_partial_no_full_history'` / `partialFallbackAccepted=true` 等)、 200 件超 chat の 122 cap 再発防止のため `<= 40` small-conversation limit で bounded。 段 B' は新規会話 case では `partial=0` で hit せず DOM fallback 経由になるが、 段 C で markdown 復元するため装飾保持。 同 commit で暫定 `console.warn('[ASA PL-053 段 B\' debug]', ...)` 削除 (= root 確証用、 削除後も `captureDebug` field 維持で snapshot debug 可)。 受け入れ smoke = (1) 新規会話 4 turn 保存で 8 件 + 見出し / 太字 / 箇条書き / link / inline code 装飾保持、 (2) reload 後保存で network strict path 優先 (= 従来品質)、 (3) 200 件超保存で strict full history 全件 (= DOM markdown fallback や 段 B' が 122 cap を full 扱いしない)、 (4) code block / table / attachment placeholder regression なし。 realtime 経路 (= 段 C 着手予定、 別 commit) では Codex Round 7 §Realtime Implication 通り stable cid authoritative buffer は network strict / bounded partial 優先、 新規 chat の no-template / `partial=0` 期間のみ 強化版 DOM fallback がデータ源、 cid 出た後 strict full history 取れたら network 置換、 DOM markdown 復元を 「既存長大 chat の append/merge」 に広げない。 第 2 cycle Step 2 段 A `54fa0c4` + 段 B `bed98e2` 上に重ねた段 B' + 段 C を 1 commit で着地 (= 論点共通 = DOM fallback 経路の保存品質改善 + bounded partial fallback)
- PL-053 第 2 cycle Step 2 段 B (`collectConversation` 厳格モード呼出移行 + DOM fallback 明示化): 段 A で追加した厳格 3 option (`forceReplay=true` / `requireFullHistory=true` / `allowPartialWithoutTemplate=false`) を `collectConversation:1342` の `requestExpandedHistory` 呼出に注入。 効果 = 「途中までしか入ってない受動 sniff cache」 を 「全件取れた」 と詐称して返す path を拒否、 active replay 強制 + 全件揃った印 (`fullHistoryReady`) が立った時のみ accept。 200 件超 chat の通常保存 122 件 cap / cache 130 件頭打ち root を消解見込み。 厳格 null 返却時の DOM fallback path を last-resort 化 = `captureDebug.captureSource = 'dom_fallback_no_full_history'` で 「partial を全件と詐称した保存」 でなく 「全件取れずに画面 DOM 走査に逃げた保存」 と明示分離 (= `networkRejectedPartial=true` 時のみ fallback ラベル化、 通常の `captureSource='dom'` ラベルは history template 不在等の 「最初から network 不可」 case 用に保持)。 既存 `networkMessages.length > 0` チェック + `orientNetworkMessages` / `fillAttachmentPlaceholders` path は無変更。 段 A 単独では機能変更 0、 段 B 着地で初めて挙動変化 = 「partial cache 経由保存」 が消えて 「strict network 経由保存 or DOM fallback 経由保存」 の 2 経路に二極化。 trade-off = site が history template 提供しない state (= cold page 初期 / 拡張 reload 直後等) で 「以前は partial 122 件で保存できてた case」 が DOM fallback (= 画面に見えてる分のみ) になる、 但し 「全件と詐称した壊れ data」 で保存するより 「画面分のみ」 と debug 明示で保存するほうが下流 (= 復旧 / 履歴管理) で破綻しない。 Codex Round 5 verdict 段階分割の 2/4。 段 C (= `start_realtime_capture` 全書換 + observer dirty trigger 専用化) 次着手予定
- PL-053 第 2 cycle Step 2 段 A (`requestExpandedHistory` 厳格モード option 追加、 土台 commit、 backward compatible): 真 root = `requestExpandedHistory:579-587` の 「partial cache 即返却 path」 が 「途中までしか入ってない受動 sniff cache」 を 「全件取れた」 と詐称して上位に返す欠陥。 200 件超 chat の 122 件 cap / 新規 chat 0 件のまま / 新 turn +2 されない 全部の発生源。 fix = signature 拡張 `requestExpandedHistory(conversationId, timeoutMs, captureDebug, domMessages, options = {})` で 3 option 受領: (1) `forceReplay` (= warm cache 短絡を skip して active replay 強制)、 (2) `requireFullHistory` (= 全件揃った印 `fullHistoryReady` が立った時のみ accept、 立たなければ null)、 (3) `allowPartialWithoutTemplate` (= false で 「履歴 template 不在 + 部分 cache のみ」 を拒否 = null 返却 + debug `replaySkipReason='no_template_partial_cache_rejected'` + `networkRejectedPartial=true` 明示)。 options 不在時 = 旧挙動完全維持 = 既存呼び元 `collectConversation:1343` への影響なし (backward compatible)。 warm short-circuit (L627) に `!forceReplay` 条件追加、 active replay 後 final return path に `requireFullHistory && !finalFullReady` で null 返却追加、 catch 後 fallback return も `requireFullHistory` 判定で null。 debug field 追加 = `forceReplay` / `requireFullHistory` / `allowPartialWithoutTemplate` / `networkRejectedPartial`。 段 A 単独では機能変更なし (= 厳格 option 呼出側未実装)、 段 B (= `collectConversation` 厳格モード呼出移行) で初めて挙動変化。 Codex Round 5 verdict (= network-authoritative realtime 採用、 既存 stable cid で DOM merge 全廃止) 段階分割の 1/4。 第 1 cycle 7 段 patch chain 全 hard reset 教訓 + Session 6 Step 2 試行 1-5e 全 NEG 教訓 = 各層 別 commit + 個別 smoke 必須
- PL-053 第 2 cycle Step 1 (passive sniff cache shrink ガード): `content.js` L487 (handlePageHookEvent) + L534 (handlePageHookMessage) の `setConversationStateBucket` 直呼出を新 helper `updateNetworkMessagesCacheGuarded` (L481-) に置換。 helper logic = 既存 cache 件数 ≥ `CACHE_SHRINK_THRESHOLD` (= 30) で 新 < 既存 のとき skip、 それ以外 (= 初期 prev 0 / 既存 < 閾値 = 新 turn 中の小規模 case) は通常上書き。 root = site lazy unmount 環境で上方向 scroll が部分 batchexecute fetch (= 14 件) を返し全件 cache を 14 件に上書き縮小、 末尾戻しでも回復せず固定する 274 件 chat 症状。 trade-off 対策 = 単純件数比較ガード (= Session 5 第 1 cycle で「新 turn partial response 1-2 件 block」 root 化) を回避するため閾値条件採用。 user smoke POSITIVE (= 既存長文 chat の上 scroll → 通常保存 / realtime とも全件取得維持、 末尾戻しでも縮小なし)。 Step 2 (realtime MutationObserver merge 化、 = 別 commit) で realtime path 全件保持化予定。 第 1 cycle 7 段 patch chain 全 hard reset 教訓 = 各 layer 別 commit + 個別 smoke 必須
- PL-054 (新規会話 save で `<style>` 要素中身が assistant turn 本文に CSS 流入する regression を修正): `content.js` `UI_SKIP` set (L889) に `style` / `script` / `template` / `noscript` 4 tag 追加。 root = `deepText` (L957-) DOM 再帰 walk で `<style>` element 子 textContent (= CSS 全文) を msg.text に accumulate、 site が `model-response` 子孫に `<style>` mount する仕様 (新規会話で頻発、 既存長文では reflow 経由で `<style>` unmount 済) で表面化。 2026-05-24 Session 5 fix-F で同 root 対処済だったが 7 段 patch chain 全 hard reset で消失、 master 復元で再表面化したのを charter PL-054 として永続化 + 1 行 patch で再着地。 `UI_SKIP` 参照 (L911, L972) 全 path に効く、 button/input 等 既存 skip 挙動互換維持。 ChatGPT/Claude 版 同 pattern audit は別 cycle
- PL-051 Phase 1.1 (assistantText 内 URL placeholder substitution): Phase 1 着地後の smoke で真 root 判明 = Gemini は画像生成 turn の text path (`turn[3][0][0][1][0]`) に **`http(s)://(...)googleusercontent.com/image_generation_content/<id>` 形式の internal URL placeholder** を返す observation。 純画像 turn drop ではなく URL を text として持つ非空 assistantText で既存 push branch を通過、 log に URL のみ表示で識別性ゼロの症状。 fix = `extractNetworkMessages` 内 `assistantTextRaw` 段階で regex `/https?:\/\/(?:[a-z0-9.-]+\.)?googleusercontent\.com\/image_generation_content\/\d+/gi` で URL 全置換 → `[AI生成]` 文字列に substitute、 後段 `extractFencedCodeBlocks` + push path は touch なし。 Phase 1 で追加した `lh3.googleusercontent.com` 検出 branch (= assistantText 空 case) は別経路防御として保持 (= image URL format の将来差替え case の保険)。 起票 trigger chat の最新 smoke log = 全 6 画像 turn で URL のみ表示確認、 substitution 後 `[AI生成]` placeholder 化想定。 title 抽出は別 cycle (= internal URL `image_generation_content/<id>` から title 逆引き不能の構造、 別 field 確証必要)
- PL-051 Phase 1 (Gemini AI 生成画像 turn placeholder 化、 防御実装): `extractNetworkMessages` (`content.js`) 内 turn loop の assistantText 空 branch に `turn[3]` JSON 文字列化 + `lh3.googleusercontent.com` URL pattern 検出経路を追加。 hit 時 `[AI生成]` placeholder で assistant msg を push。 背景 = Claude PL-039 Phase 1.1 + ChatGPT PL-050 で 3 版整合 `[AI生成: <title>]` / `[画像: <title>]` placeholder contract 確立後、 user 報告で Gemini 版だけ純画像生成 turn (= 「江戸時代風のタッチでろくろ首描いて」 等 prompt → AI 画像生成) で assistantText path (`turn[3][0][0][1][0]`) 空 → turn 完全 drop の症状確認。 起票 trigger chat = `https://gemini.google.com/app/fe873adc9e61459f`、 ASA log = user msg のみ、 assistant turn 完全消失 (placeholder すら不在)。 field 位置確証 path = probe script 用意済 (`Gemini_ASA/probe_gemini_image_field.js`) だが console paste workflow で user 操作多すぎ scrap、 代わりに 「image URL は `lh3.googleusercontent.com` 含む = JSON 文字列化で文字列 hit すれば画像 turn 確証」 の防御 pattern 採用、 field 位置推測なし。 既存挙動完全保持 (= assistantText 非空 case は touch なし、 file 添付 placeholder logic も touch なし)、 純画像 turn (= text 不在 + image URL あり) のみ救済。 title 取得は Phase 1.1 別 cycle (= ChatGPT PL-050 と同 2 段階 pattern)、 現段階は枠保持 = 識別子は title なしの仮 placeholder
- PL-036 Phase 3 (event-driven wait): `requestExpandedHistory` (`content.js`) の wait loop を timeout polling (`while + sleep(100)` × N) から Promise + event listener race に置換、所要時間バラつきを deterministic 化。背景 = Phase 2 着地後 270 message conv で wait loop break ~18s + バラつき σ ±数秒 観測、charter §期待動作 「所要時間バラつき < ±20%」未達。`fullHistoryReadyByConversationId` flag が set される瞬間 = passive sniff endOfHistory 受信時 (`handlePageHookEvent` / `handlePageHookMessage` parsed.endOfHistory branch) または active replay 完遂時の page_hook bridge `full-history-ready` message 受信時、両 path で flag set 時に subscribe 中の waiter を resolve する設計。state 追加 = `state.historyReadyWaitersByConversationId` (cid → resolver 配列)、helper 追加 = `registerHistoryReadyWaiter(cid, resolve)` (subscribe + unregister 関数返却) / `notifyHistoryReadyWaiters(cid)` (bucket drain + resolver 全 fire、変種 cid 対応 + double-fire 防御 Set guard) / `setFullHistoryReady(cid)` (set + notify pair 集約、3 site 流入経路の notify 抜け防止)。`requestExpandedHistory` 内 polling loop + `smallConversationGraceMs` fallback (1200ms grace + fullOverlap 短絡) を全削除、`await new Promise((resolve) => { register; if (already-ready) resolve immediate; setTimeout(resolve, timeoutMs) })` race 化、subscribe 前に flag 既 set な race を `getConversationStateBucket` 即時 check で救済。`finally` で `clearTimeout` + `unregisterWaiter` で leak 防止。`smallConversationGrace` 撤去 = polling 撤去で技術的に自然消滅 (event-driven 後は loop 自体無)、別 commit 分離不能。orphan 化した `sleep(ms)` helper 削除 (唯一 caller が wait loop polling)。captureDebug `replayShortCircuitReason` reset 行も grace fallback 撤去で writer 不在となり削除 (init 側 `collectConversation` L1198 + reader whitelist `snapshot_acquire.py` は Phase 2 inherited dead と一括別 PL で sweep 予定)。content.js 1838 → 1861 (+23、charter file_total +/-30 内、+50 stamp 余裕)。smoke 4 サンプル POSITIVE: 失敗 conv 122 msg 平均 6.39s σ ±7%、270 msg conv (274 msg) 平均 19.56s σ **±1%** (deterministic 達成)、中短文 96 msg 3.86s、短文即時。19.56s は paginate 14 batch × ~1.3s 律速で charter §期待動作 「100 turn 超 ≤ 15 秒」目標 +4.5s 超過、Phase 5 batch size / delay tune 候補 (Phase 3 真目標 = random latency 解消は ±1% で完全達成、release blocker 無)。競合「AIチャットエクスポーター」(auto-scroll DOM) 比 = 失敗 conv 3.6x / 270 msg 4.5x / 96 msg 4.6x 速 + bot-detection safe 維持
- PL-036 Phase 2 regression fix (active replay fetch hook bypass、bucket replace race 解消): `replayHistoryRequest` (`page_hook.js`) 内 fetch 呼出を `await fetch(...)` から `await originalFetch.call(window, ...)` に変更、自身が install した fetch hook (`window.fetch = wrapped function`、line 401-) を bypass。背景 = wrapped fetch は response を `clone.text().then(emit)` 経由で content.js bucket に流入させる設計、active replay 各 batch の生 response が emit() 発火して content.js `state.networkMessagesByConversationId` を replace、最終 batch (turns < BATCH_SIZE cursor null) の async emit が microtask 遅延着で synth emit (accumulated 全件) の後に bucket を late overwrite する race 発生。失敗 conv (61 turn 累積後 last batch turns=1 で bucket=1 turn=2 message に上書き) で確定、wait loop poll が overwrite 後 state を観察 → no growth → 30s timeout fall-through → final=2 NEGATIVE。Phase 2 270 message conv POSITIVE は同 race ながら synth postMessage 処理と wait loop sleep tick の偶然 timing で synth state catch していた sample bias と判明。fix = active replay の fetch を hook 通過させず、accumulate 完了後 1 度の synth emit のみで bucket 更新する設計に統一、各 batch 生 response 流入経路を完全 cut。`originalFetch` は IIFE 同 scope で line 401 const 定義済、`replayHistoryRequest` は event handler 経由で IIFE init 完了後にのみ呼ばれるため TDZ 安全。270 message conv smoke + 失敗 conv smoke + 短文 conv smoke 3 サンプル全 POSITIVE で確認 (61 turn = 122 msg / 137 turn = 274 msg / warm short-circuit hit 即時)
- PL-036 Phase 2 (paginate logic + lengthMatch / overlap 系 厳密化 + race / 2 重 fire 解消): `replayHistoryRequest` (`page_hook.js`) を continuation token loop 化、batch size 10 (UI mimicry)、batch 間 humanized delay 100-300ms (charter §invariant #4 ban-avoidance 余裕保持の最低ライン)、最大 100 batch safety net、accumulate 完了後 synthetic batchexecute response (`)]}'\n[[["wrb.fr","hNvQHb",JSON.stringify([turns,null,null,null]),null,null,null,"generic"]]]`) を 1 度 emit して既存 `extractNetworkMessages` replace 経路で全 turn cache 化。`extractNetworkMessages` に `endOfHistory` field 追加 (response inner[1] cursor 不在 or turns < 10 で true)、handler 経路で `state.fullHistoryReadyByConversationId` set。新 bridge message type `full-history-ready` (page_hook → content.js) で synth emit 後 flag set。`requestExpandedHistory` warm short-circuit を `fullHistoryReady` 単独 condition に集約 (`fullOverlap` / `tailOverlapReady` / `lengthMatch` 全て削除)。理由 = 270 message conv の diagnostic で `fullOverlap` が passive sniff 部分 (20 turn) と DOM 部分 (20 turn) の偶然 contents 一致で hit する false positive trap と判明、両方部分 truncate のとき contents 一致しても全件 signal にならない設計欠陥。passive sniff の paginate end signal と active replay 完遂時の full-history-ready signal の両 path 共通の真の全件 signal として fullHistoryReady 単独採用。`inFlightReplayByConversationId` guard で `requestExpandedHistory` の `postMessage` + `dispatchEvent` 両 path 受信による page_hook 内 paginate loop 並行 2 周進行を防御。`requestExpandedHistory` call site timeout 8000 → 30000ms (charter §期待動作 「100 turn 超 ≤15 秒」+ paginate 完遂 buffer)。content.js `limit:100` hardcode 撤去 (page_hook 内 batch size 制御)。`replayHistoryRequest` signature 変更 (`limit` 引数廃止)。cursor 構造は probe (commit `6358213` / `8728b22` / `5c8cdcd` / `b899b1f`) で確定 = response inner[1] = base64-like cursor、next request inner[2] に格納。270 message (137 turn) conv smoke POSITIVE = wait loop break ~18s で全 274 message 取得確認 (charter 目標 ≤15s より 3s オーバーは Phase 3 event-driven wait で改善余地、Phase 2 scope は完遂)
- PL-036 Phase 1 (warm-cache fast-path 強化): `requestExpandedHistory` warm short-circuit 条件 (`content.js:453`) から `fullHistoryReady` flag 依存を除去 + `lengthMatch` (`before === domCount`) を追加 short-circuit signal 化。初回 save click 時の 8s timeout 全消費を解消。背景 = passive sniff 経路に `fullHistoryReady` flag set 経路無 → 既存条件で初回 entry 不可、加えて overlap fingerprint が markdown delimiter 差で部分一致しか取れない conv (smoke で 10 turn 中 normal=5/reversed=1/tail=2 観測) では fullOverlap / tailOverlapReady 共に成立せず replay no-growth → 8s 全消費 fall-through path に到達。length match (passive sniff turn 数 = DOM turn 数) を full-history signal として扱う設計で短絡 hit 化、短絡 hit 時に `state.fullHistoryReadyByConversationId` set + `replaySkipReason: warm_cache_length_match` を debug 出力。10 turn 会話 save 体感 8s → 即時 (smoke POSITIVE 観測)。`buildDomMessages` (同期高速) touch 0、replay post path / smallConversationGrace fallback 経路無傷で regression 影響なし。Phase 2 paginate 着地時に >100 turn case の length match 厳密化予定
- widget tooltip を native `title=` 属性から自前 CSS tooltip (`::after` pseudo-element + `data-tooltip` attr) に置換。OS native tooltip の遅延 (~500ms) / 配色 / 位置ばらつき 3 症状を一括解消。Shadow DOM scope 内で `padding: 3px 8px 1px` (日本語 baseline 視覚補正) / `line-height: 1.3` (button line-height 36px 継承遮断) / button 直上 default + 上端余白 < 40px で下方向 flip (`mouseenter` で `getBoundingClientRect` → `data-tooltip-pos` attr toggle) / 120ms hover delay。`applyWidgetI18n` の `el.title = text` は `el.dataset.tooltip = text` に置換 (`aria-label` は維持)。**Gemini 本サイト theme 連動** = page light = 黒bg/白文字 (default) / page dark = 白bg/黒文字 (`:host([data-theme="dark"])` branch、軽 box-shadow 付与)。`bindWidgetTheme` を `prefers-color-scheme` (OS) → `document.body.classList.contains('dark-theme')` (page) sniff に切替 + `MutationObserver` で `body.class` 変化を reactive 監視。Claude / ChatGPT 本サイトは tooltip 黒固定のため 2 版は theme 連動せず黒固定維持
- PL-029 CP3 (`background.js` ASA pre-warm on tab probe、ChatGPT CP2 横展開): `probeGeminiTab` 関数先頭に `void ensureAsaRunning()` (fire-and-forget) を挿入。tab probe = ASA 必要性 signal の proactive trigger 化、tab probe 完了時点 ≈ ASA spawn 並行完了で 1st widget click 時の体感 lag (~300-500ms) 解消。`ensureAsaRunning` singleton (PL-025 CP5) + `state.token` early return guard で重複 spawn / 副作用 0、onUpdated 連発 / bootstrap 起動時 4 回 trigger でも in-flight share or token guard で抑制完備。reactive 経路 (postEvent 401/403 + PL-028 recovery) は touched せず共存。ChatGPT 1 版先導 (CP2) を 1:1 展開
- PL-029 CP4 (`background.js` NMH old port disconnect 化、ChatGPT 同 commit 1:1 横展開): `ensureAsaRunning` 内 `state.nmhPort = port` 上書き前に旧 port を `disconnect()` 呼出。`chrome.runtime.connectNative` で得た port は明示 disconnect しない限り alive 維持 = NMH helper (bat + python.exe = 2 process) も alive 維持で service tab close + 再 open 反復で **NMH helper process が累積する root** を能動 disconnect で旧 helper の stdin EOF → exit させて回避。PL-025 CP5 「NMH disconnect ≠ ASA exit」と整合 (ASA 本体は別 process で alive 維持)
- PL-029 CP5 (`background.js` tab_closed gate 内 NMH disconnect + token reset、ChatGPT 同 commit 1:1 横展開): `tab_closed` gate (PL-021 CP2、残 service tab 0 で `postEvent('tab_closed', ...)` 発火) の **直後**に `state.nmhPort.disconnect()` + `state.token = null` reset を追加。ASA shutdown 確定時に NMH helper も同時 exit で **Python 残置 0** 化、かつ次 service tab open 時の `ensureAsaRunning` が `state.token === null` 経路で **pre-warm 段階で full spawn 発火** = postEvent recovery (PL-028) 待ちの 1st click lag 解消。「(2) 残置 + 再 open で slow spawn」症状を根治
- PL-028 CP4 (`background.js` ASA crash 時 token stale 復旧、ChatGPT CP2 横展開): `postEvent` に `_retried` guard 付き 1 回 retry 経路追加。`fetchError === true` / `status === 0` (ECONNREFUSED = ASA 本体 HTTP server 消失) または `status === 403` (token mismatch、限定 case) を検出して `state.token = null` clear + `ensureAsaRunning` 再実行 + 元 `postEvent` を 1 回 retry。ChatGPT 1 版先導 (CP2) を 1:1 展開、`tool: 'gemini_asa'` 差分のみ。PL-025 CP3 fix-C で削除した `port.onDisconnect` token reset の副作用補完
- PL-025 CP5: async push (`collectConversation` 数分 wait) の bug/正常 区別不能 UX を解消。`content.js` `bindWidgetButtons` の save_open 分岐を split events 化 = `widget_save_pending` 即送 → `collectConversation` + `buildSnapshot` await → `widget_save_open` snapshot 同梱送信。ASA 側は CP2 着地 `show_save_pending_dialog` (modal Tk dialog `保存準備中…`) を pending 受領で表示、open snapshot 受領で dismiss + `_save_once_impl` 実行。`background.js` `ensureAsaRunning` を `_ensureAsaRunningPromise` singleton in-flight promise 化 (CP3 fix-A 横展開) + `port.onDisconnect` 内 `state.token = null` reset 削除 (CP3 fix-C 横展開)。pending 即送は singleton race fix と併用の防御 2 段 (race は fix-A 単独で消えるが、`await sendMessage` で逐次保証も併せて行う)。Gemini は async push のため split events の主たる適用対象 = sync 経路 (ChatGPT/Claude) の visual ack より UX 価値が高い (charter §scope 縮退判断)
- PL-021 CP2 (CP4 横展開): `background.js` `tab_closed` handler に `chrome.tabs.query({url: 'https://gemini.google.com/*'})` gate 追加 = sender tab 除く残 service tab 数 0 のときだけ `postEvent('tab_closed', ...)` 発火。chrome multi-window / multi-tab で 1 枚閉じだけで ASA 連鎖閉鎖していた root cause を event 元で抑止。ChatGPT 1 版先導 (`86e6b61`) を Gemini に同形展開。manifest `host_permissions` `https://gemini.google.com/*` 既存 = 追加権限不要
- PL-007: scroll-snap 経路の `<table>` を markdown pipe 構文に変換し保存 HTML での表フラット化を解消 (`4428905`)。`deepText` 内に `tableToMarkdown` を追加、`<thead>/<tbody>/<tfoot>` 跨ぎ・列数不揃い・header 検出に対応
- PL-016: code-block を `[[ASA_CODE_BLOCK_<idx>]]` マーカーで `text` 内に置換し、本体を `code_blocks` 配列に積んで snapshot に同梱。`<pre>` / `<code-block>` tag / `class="code-block"` を捕捉、二重 wrap は内側優先で抑止。md は marker → 平文戻し（現状維持）、html は fenced code block に展開
- PL-016 Step 2: 外側 `<code-block>` 到達時に `div.code-block-decoration` から言語ラベル ("C"/"HTML"/"Python" 等) を抽出し `code_blocks[idx].lang` に格納、ラベルテキストを本文から除去。html で `<pre><code class="language-X">` として描画、独立 `<p>Python</p>` 段落消失。3 版で `code_blocks[].lang` contract 整合 (Claude PL-011 / ChatGPT 未実装)
- PL-017: file-only upload (本文空 + 添付のみ) で user 発言が保存出力から消失する問題を修正。3 経路で対応: (1) `deepText` で `user-query-file-preview` 到達時に `button[aria-label]` から完全 file 名を抽出し `[添付ファイル: <name>]` placeholder text を本文に注入、(2) `extractNetworkMessages` で userText 空 + assistantText 非空の turn に placeholder user message を push して turn 順序を維持、(3) `fillAttachmentPlaceholders` を `collectConversation` の orient 後に挿入し、network 経路 placeholder を DOM walker 由来 `[添付ファイル: <name>]` で順序対応置換。`button` の `UI_SKIP` 維持 (sidebar / copy button noise 抑止) で既存挙動と互換
- PL-017 拡張 (mixed turn): 本文 + 添付の混在 turn で network userText が非空のため `_attachmentPlaceholder` flag が立たず file 名が完全消失していた regression を解消。`fillAttachmentPlaceholders` を改修し、dom user msg の各 user turn から placeholder 行のみを `_extractAttachmentPlaceholderLines` で抽出して network user msg と 1:1 対応で処理: flag あり (file-only) → 完全置換、flag なし + placeholder あり (mixed) → 本文先頭に prepend、placeholder なし → そのまま。3 版 (Claude PL-012 mixed turn 対応 / ChatGPT PL-037 file-tile 内側構造) と整合
- PL-017 拡張 (画像 file 名 network 経路抽出 + a11y label 混入除去): 画像で `[添付ファイル: アップロードした画像をライトボックスに表示]` のような lightbox button の UI 操作 label を file 名として誤採用する問題、および mixed turn で「あなたのプロンプト」 a11y label が `[添付ファイル]` 注入後に startsWith 判定をすり抜けて残存する問題を解消。修正 3 点: (1) `deepText` 入口で `cdk-visually-hidden` / `screen-reader-` prefix の class を持つ要素を subtree ごと skip し a11y noise を要素単位除去、(2) `extractNetworkMessages` に `extractFileNamesFromTurn(turn)` ヘルパ追加し、batchexecute response の `turn[2][0][4][0][3][N][2]` から元 file 名を抽出 (画像/PDF 全種別共通の機械可読 metadata、UTF-8 完全形)、userText に `[添付ファイル: <name>]` として prepend (file-only / mixed 両対応、`_attachmentPlaceholder` flag 不要)、(3) `fillAttachmentPlaceholders` の mixed prepend 段に dedup guard 追加 (network text に既に `[添付ファイル` が含まれていれば dom 由来 placeholder の prepend を skip)。DOM 経路 (`user-query-file-preview` button[aria-label]) は network 空応答 fallback として原状保持。Gemini DOM の `user-query-file-preview` には画像の元 file 名が露出しない (button aria-label が UI 操作 label、img.alt も generic、src は blob: URL) ため network response payload を一次情報源とする設計に転換
- PL-015 (Claude PL-014 横展開): `dedup` 関数の `fingerprint(tag, text) = tag + text.slice(0, 80)` text-based dedup を occurrence-based に書換、**異 DOM node の同 text** msg を collapse する副作用を解消。template 連投応答 / regenerate で同 content 連発する case で全経路 (single / realtime / scroll_snap) の最終 turn が消失する症状を全 mode 共通で fix。`buildDomMessages` 内の唯一 dedup 経路 (line 1031) に作用、network 拡張 path にも反映される (`buildDomMessages` 結果が baseline)。同 input 内 N 回目の同 text は同 DOM msg として dedupe、N+1 回目は別 msg として preserve

## 1.0.2 (2026-04-08)

- 初回 Chrome Web Store 申請版
- `manifest.name` を「ASA Bridge for Gemini」に確定
- プライバシーポリシー URL: https://jellycotton.github.io/asa-privacy-policy/
