Skip to content

fix(mobile): screenshot/file downloads now work on iOS Safari (#833)#837

Merged
takaokouji merged 3 commits into
developfrom
topic/autopilot-833
Jul 1, 2026
Merged

fix(mobile): screenshot/file downloads now work on iOS Safari (#833)#837
takaokouji merged 3 commits into
developfrom
topic/autopilot-833

Conversation

@smalruby3-editor-bot

Copy link
Copy Markdown

Summary

モバイル(特に iOS / iPadOS Safari)でプログラムのスクリーンショット撮影ボタンが「動かない」(無反応)不具合の修正です。

  • Code タブ: BlocksScreenshotButtonblocks-screenshot-button_screenshotButton_*
  • Ruby タブ: カメラアイコン(ruby-tab_zoomButton_* / data-testid="ruby-screenshot"

原因

画像生成(SVG→canvas→PNG / Monaco→PNG)自体は成功していますが、最終的な保存はどちらも共有ヘルパー src/lib/download-blob.js<a download>.click() 方式に依存しています。iOS / iPadOS Safari は anchor の download 属性を無視するため、'download' in HTMLAnchorElement.prototypetrue でも実際の保存が起きず、ユーザーには「何も起きない」ように見えます。

この download-blob.js は 7 箇所(スクショ2経路、sb3 保存、コスチューム/音/モニタ出力)から使われる共有ヘルパーなので、パターンの根本(中央)を直しました。

Changes Made

  • src/lib/download-blob.js(upstream ファイル, Smalruby マーカー付き)に保存方式の分岐を追加:
    • デスクトップ: 既存の anchor download クリック(挙動変更なし)。
    • iOS / iPadOS: Web Share API(navigator.share({files}))で「写真 / ファイルに保存」。共有不可なら blob を新規タブで開いて長押し保存にフォールバック。
    • レガシー Edge(msSaveOrOpenBlob)は従来どおり。
    • iPadOS 13+ は desktop Safari を詐称するため maxTouchPoints で判定。Android Chrome は download が効くのでモバイル扱いにしない(無用な回帰を避ける)。
  • 経路選択を純粋関数 chooseDownloadStrategy() に切り出し、isAppleMobile() とともに jest で単体テスト。
  • マーカー一覧(docs/maintenance/smalruby-markers-gui.md)と .prettierignore ホワイトリストを更新。

Test Coverage

  • test/unit/lib/download-blob.test.js(新規, 12 tests):
    • chooseDownloadStrategy: msSave / desktop=anchor / mobile+share / mobile→newTab / mobile が anchor を選ばない / 旧ブラウザ=newTab。
    • isAppleMobile: iPhone / iPad / iPadOS13+(touch) / 実 Mac(非touch=false) / Android(false) / Windows(false)。
  • 既存の blocks-screenshot.test.js / ruby-screenshot.test.js も green(回帰なし)。
  • lint clean(worktree 未ビルドの microbit generated 由来の 2 件は本変更と無関係。CI は build:dev 後に lint するため通過)。

注: 実機モバイルの DL 挙動は jsdom で再現できないため、本 PR の自動テストは経路選択ロジックを検証します。

⚠️ 実機確認のお願い(HITL)

実機モバイルでの保存挙動は人間による確認が必要です。プレビュー環境で以下を確認してください:

  • iPhone Safari — Code タブのスクショボタン → 共有シート(または新規タブ)が出て画像を保存できる
  • iPhone Safari — Ruby タブのカメラボタン → 同上
  • iPad Safari — 上記2つが同様に動く
  • デスクトップ Chrome/Firefox — 従来どおり PNG が直接ダウンロードされる(回帰なし)
  • (任意)Android Chrome — 従来どおり直接ダウンロードされる

Related Issues

Closes #833

iOS/iPadOS Safari silently ignores the anchor `download` attribute, so the

screenshot buttons (Code tab BlocksScreenshotButton, Ruby tab ruby-screenshot)

and other blob downloads produce no visible result on mobile.

Add a mobile-friendly path in the shared download-blob helper: on iOS/iPadOS

it uses the Web Share API (save to Photos/Files) when files are shareable,

and otherwise opens the blob in a new tab for long-press save. Desktop keeps

the existing anchor-click behavior unchanged. The branch-selection logic is a

pure function (chooseDownloadStrategy) covered by jest unit tests.

Real-device mobile save verification is left to a human (HITL).

Refs #833

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@smalruby3-editor-bot smalruby3-editor-bot Bot added the 🤖 autopilot autopilot(AI)が管理する PR/Issue(AI処理対象) label Jun 29, 2026
@smalruby3-editor-bot

smalruby3-editor-bot Bot commented Jun 29, 2026

Copy link
Copy Markdown
Author

🤖 autopilot status

field value
Status DoD
AI Status
HITL Yes
Size

🧪 DoD 引き継ぎあり — このスレッドの autopilot:dod-handoff コメント(headful Playwright の検証手順)を参照。

Linked issue #833. Maintained by autopilot (single writer); do not edit.

@github-actions

Copy link
Copy Markdown

🚀 Preview deployed: https://smalruby.jp/smalruby3-editor/topic/autopilot-833/

navigator.share rejects with AbortError on a deliberate user cancel.
The previous catch treated cancel and failure alike, so cancelling the
iOS share sheet surprised the user with a new tab. Only fall back to
opening the blob on a genuine share failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

🤖 敵対的レビュー (autopilot-review)

差分を批判的観点でレビューしました。1 件は安全に自動修正済み、もう 1 件は実機確認で必ず見てほしい設計リスクとして残します。

✅ 自動修正済み — 共有キャンセル時の不要な新規タブ

navigator.share() はユーザーが共有シートを意図的にキャンセルすると AbortError で reject します。修正前の catch はキャンセルと失敗を区別せず、キャンセル時にも openBlobInNewTab を呼んで意図しない新規タブを開いていました。err.name === 'AbortError' のときはフォールバックしないように修正しました(commit 447679aa3b)。

⚠️ 実機確認で要注目 — user activation の喪失(自動修正なし)

これがこの PR の最大のリスクです。downloadBlob は常に非同期の画像生成後に呼ばれます:

blocks-screenshot.js: fetch()Image デコード → canvas.toBlob(cb) のコールバック内で downloadBlob 呼び出し(ruby-screenshot.js も同様に Monaco→PNG の async 後)。

navigator.share()window.open()transient user activation(クリック由来、約 5 秒で失効・消費される)を要求します。上記の fetch+画像デコードを挟むと、呼び出し時点で activation が失効している可能性が高く、その場合:

  • navigator.share()NotAllowedError で失敗
  • フォールバックの window.open()iOS Safari のポップアップブロックで同じく失敗

→ 結果として「ボタンを押しても何も起きない」という元の症状が iOS 実機で残る恐れがあります。これは自動テスト(jsdom)でも desktop Playwright でも再現できないため、実機確認が必須です。

実機確認時のお願い:

  • iPhone/iPad Safari で Code タブ・Ruby タブのスクショボタンを押し、共有シート(または新規タブ)が実際に出るかを確認してください。
  • もし「何も起きない」場合、原因は activation 失効である可能性が高いです。その場合の根本対策は「クリックハンドラ内で同期的に window.open('', '_blank') などでタブ/共有を予約してから、async 生成後にそのタブへ blob を流し込む」設計(旧 iOS12 経路に近い形)への作り替えになります。複数呼び出し元(スクショ2経路+sb3/コスチューム/音/モニタ)に跨る変更で自動修正はリスクが高いため、実機で再現したら別途対応を提案します。

補足(ブロッカーではない)

  • download-blob.js は 7 経路の共有ヘルパーなので、iOS では sb3 保存等も share/新規タブ経由になります。iOS では従来から download 属性が無視され DL 不可だったため回帰ではなく改善方向ですが、sb3(zip)を新規タブで開いても保存しづらい点は実機で挙動を確認してください。
  • 経路選択ロジック (chooseDownloadStrategy / isAppleMobile) のテストは妥当。dispatch(share/newTab 実呼び出し)は jsdom 制約で未テストですが、Issue の方針どおりで OK。
  • lint / prettier / 既存テストはグリーン(markers doc は repo root にありパッケージ単位の prettier 対象外なので既存の整形差分は CI 非該当)。

@smalruby3-editor-bot smalruby3-editor-bot Bot added the 🙋 HITL 人間の対応待ち(レビュー/判断/マージ) label Jun 29, 2026
@smalruby3-editor-bot smalruby3-editor-bot Bot marked this pull request as ready for review June 29, 2026 17:33
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

🧪 DoD 引き継ぎ(headful Playwright / ホスト Claude 用)

このコメントは DoD フェーズの引き継ぎドキュメントです。コンテナ内 autopilot は headless の
ため実ブラウザ確認ができません。**ホスト側のあなたの Claude(headful Playwright MCP)**で以下を
実施し、結果をこの PR にコメントしてください。

--- 以下をそのままホストの Claude に貼り付けてください。


役割: PR smalruby/smalruby3-editor #837(Issue #833)の DoD を headful Playwright MCP(ホストの実 Chrome)で
検証
してください。autopilot はコンテナ内で headless のためあなたに引き継ぎます。

対象

  • PR: smalruby/smalruby3-editor fix(mobile): screenshot/file downloads now work on iOS Safari (#833) #837 / ブランチ topic/autopilot-833
  • プレビュー(CI ビルド済み・dev server 不要): https://smalruby.jp/smalruby3-editor/topic/autopilot-833/
  • 変更点の要約は PR 本文を参照。検証前に PR の CI が green であることを確認すること
    (daemon は CI 状態をチェックせずこの引き継ぎを生成している)。

事前準備

  • ホストの Chrome を Playwright MCP で headful 操作する(reference_host_playwright_mcp.md の構成)。
  • URL には常に ?no_beforeunload=1 を付ける。
  • 秘密情報(バイパストークン等)は本文・ログに残さないこと。必要ならローカル .env の値を
    読み、URL パラメータに使うだけにする(値はコメントに書かない)。

検証手順(Issue の DoD チェックリスト)

以下は Issue #833 の DoD をそのまま転記したもの。各項目を headful で確認し、結果を記録する。
スクショは tmp/ に保存し、UI に視覚的変更があれば docs/<feature>/screenshots/ に既存の命名規則
docs/_screenshot-guidelines.md)で追加してブランチに追加コミット(あなたの手元で commit/push)する。

  • 原因(モバイルで DL が発火しない経路)をコードで特定し、コメントで説明
  • モバイルでスクショ画像を保存/取得できる経路を実装(Code・Ruby 両タブ)。デスクトップは回帰なし
  • DL 経路選択ロジックの jest テストを追加
  • 実機モバイル確認は HITL で人間に依頼(PR にチェック手順を記載)

報告(完了の出口)

  • すべて OK: この PR smalruby/smalruby3-editor fix(mobile): screenshot/file downloads now work on iOS Safari (#833) #837 に「DoD 検証 OK」+ スクショ要約をコメント。スクショ commit を push。
    → 人間が merge して Close(既存 merge-progression が leaf を Close)。
  • NG / 要修正: NG 項目を PR にコメントし、🙋 HITL ラベルを外す → autopilot が DoD を解除して
    address-review
    を起動し、差分と全コメントを読んで対応します(Review と対称)。
  • 判断に迷う: 論点を PR にコメントして人間に確認。

検証は 本番 Chrome で行うこと(Playwright の Chromium はポリシーが緩く誤検知することがある)。

@takaokouji takaokouji merged commit 60bd7c6 into develop Jul 1, 2026
9 checks passed
@takaokouji takaokouji deleted the topic/autopilot-833 branch July 1, 2026 00:22
github-actions Bot pushed a commit that referenced this pull request Jul 1, 2026
…ot-833

fix(mobile): screenshot/file downloads now work on iOS Safari (#833)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 autopilot autopilot(AI)が管理する PR/Issue(AI処理対象) 🙋 HITL 人間の対応待ち(レビュー/判断/マージ)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(mobile): モバイルでカメラボタンが動作しない(要再現確認)

1 participant