Skip to content

コマンド新規作成時、Hubへの共有を誘うToastを表示する - #444

Merged
ujiro99 merged 7 commits into
dev-1.1.1from
feat/hub-share-toast
Aug 8, 2026
Merged

コマンド新規作成時、Hubへの共有を誘うToastを表示する#444
ujiro99 merged 7 commits into
dev-1.1.1from
feat/hub-share-toast

Conversation

@ujiro99

@ujiro99 ujiro99 commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • コマンドを新規作成した直後に、Selection Command Hubへの共有を促すToastをオプションページに表示する
  • 一度表示したら、そのユーザーには二度と表示しない(hasShownHubShareToastフラグ、既存のhasShownReviewRequestと同じ配線パターン)
  • ShareButtonにあった共有可否判定ロジックをisHubShareable()としてhubShare.tsに切り出し、Toast側と共通化

実装内容

  • HubShareToast.tsx(新規): ReviewRequestToast.tsxを参考に、sonnertoast.custom()でToastを表示
  • CommandList.tsx: コマンド新規作成時(commandUpsertのappend分岐)にToastをトリガー
  • Option.tsx: sonner<Toaster/>をオプションページに追加(従来未マウントだった)
  • 全14ロケールのmessages.jsonhub_share_toast_message/_button/_laterキーを追加

Test plan

  • yarn build が通ること
  • yarn lint でエラーがないこと
  • yarn test:run(977件)が全てパスすること
  • yarn test:run src/test/locales.test.ts で全ロケールのキー整合性が保たれていること
  • ビルド済み拡張機能をPlaywrightで実際にロードし、以下を確認
    • 新規コマンド作成後にToastが表示される
    • 「Later」で閉じるとフラグが永続化される
    • 2回目のコマンド作成ではToastが再表示されない

Closes #443

🤖 Generated with Claude Code

https://claude.ai/code/session_01EJVeN7n8jzatkUy99SGyhG

Shows a one-time toast on the options page after a command is created,
inviting the user to share it to the Selection Command Hub. Once shown,
it is never shown again (hasShownHubShareToast flag), following the
same pattern as the existing review-request toast.

Closes #443

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJVeN7n8jzatkUy99SGyhG
@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.35484% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.67%. Comparing base (7564109) to head (ffe40a0).

Files with missing lines Patch % Lines
...es/extension/src/components/option/ShareButton.tsx 0.00% 3 Missing ⚠️
...s/extension/src/components/option/ImportExport.tsx 0.00% 2 Missing ⚠️
...ackages/extension/src/components/option/Option.tsx 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #444      +/-   ##
==========================================
+ Coverage   39.56%   40.67%   +1.10%     
==========================================
  Files         237      238       +1     
  Lines       25353    25443      +90     
  Branches     1886     1923      +37     
==========================================
+ Hits        10032    10348     +316     
+ Misses      15321    15095     -226     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

コードレビュー

新規コマンド作成後にHub共有を促すToastを表示する機能追加、およびShareButtonの共有可否判定ロジックをhubShare.tsisHubShareable()に切り出すリファクタ、拝見しました。全体的に既存のReviewRequestToastパターンを踏襲した実装で、構成は分かりやすいです。いくつか気になった点を記載します。

1. 「一度だけ表示」の保証が崩れるケースがある(中)

packages/extension/src/components/option/HubShareToast.tsx:16-72

showHubShareToastのJSDocには「onShownはどちらのボタンを押しても呼ばれる」「一度表示したら二度と表示しない」と書かれていますが、実際にonShown()が呼ばれるのは「Later」または「Share」ボタンをクリックした場合のみです(38-41行目、50-61行目)。

  • Toastはduration: 60 * 1000(60秒)でタイムアウトすると自動で閉じますが、その際onShownは呼ばれません(sonnerのonAutoClose等のハンドラが未設定)。
  • また、ユーザーがボタンを押さずにオプションページから離脱した場合も同様です。

この場合hasShownHubShareToastフラグが立たないため、次に新規コマンドを作成したタイミングで再度Toastが表示されてしまい、PR概要および実装コメントで謳っている「一度表示したら二度と表示しない」という仕様と食い違います。ReviewRequestToastpackages/extension/src/components/App.tsx:59-61)も同様の作りですが、そちらは「レビューを書く」を押した時のみフラグを立てる設計であり、「タイムアウトで消えたら再表示されうる」という前提が矛盾なく成立しています。今回はコメントで明確に「一度きり」を謳っているため、意図と実装のズレとして気になりました。

対応案としては、toast.customの第2引数にonAutoClose/onDismiss相当のコールバックを渡してタイムアウト時にもonShownを呼ぶか、あるいはJSDocの記述を実態(ボタン操作時のみフラグを立てる)に合わせて修正するのが良さそうです。

2. 新規追加ロジックのテストが無い(軽微)

  • isHubShareable()packages/extension/src/services/hubShare.ts:82-92): ShareButtonから切り出された既存ロジックですが、hubShare.test.tsには対応するテストケースが追加されていません(既存のShareButton側にもテストファイルが存在しないため、この切り出しによって初めてユニットテスト可能になった関数です)。
  • HubShareToast.tsx: コンポーネント自体のテストが未追加です。
  • CommandList.tsx:244-253の新規コマンド作成時のToastトリガー分岐(hasShownHubShareToastのチェック、isHubShareableとの組み合わせ)にもテストがありません。

PRのTest planには手動でのPlaywright確認は含まれていますが、ロジック部分(特にisHubShareableの各条件分岐)はユニットテストで保護しておくと今後のリグレッションに気づきやすくなると思います。

3. 軽微な指摘

  • packages/extension/src/components/option/editor/CommandList.tsx:244-253: Settings.get()の完了を待たずに複数のコマンドを連続作成した場合(考えにくいですが)、hasShownHubShareToastのチェックが競合し、Toastが2回表示される可能性があります。実用上のシナリオとしては稀なので優先度は低いです。
  • hub_share_toast_laterのi18nキーには英語(en)を含めdescriptionフィールドが付与されていません(hub_share_toast_message/_buttonにはある)。他のキー(例:review_request_later)と同様の省略なので大きな問題ではありませんが、Chrome拡張のi18nガイドライン的にはdescriptionがあった方が翻訳者への情報として親切です。

良い点

  • ShareButton.tsxの共有可否判定をisHubShareable()としてhubShare.tsに切り出したことで、Toast側とロジックを共通化できており、DRYになっています(packages/extension/src/services/hubShare.ts:82-92, packages/extension/src/components/option/ShareButton.tsx:6-10,61)。
  • hasShownHubShareToastフラグの配線(defaultSettings.ts, enhancedSettings.ts, settings.ts, storage/index.ts, types/index.ts)が既存のhasShownReviewRequestと一貫したパターンで実装されており、抜け漏れが無いことを確認しました。
  • 14ロケール全てに翻訳キーが漏れなく追加されています。
  • sourceTypeCommandEditDialog.tsx:123で新規作成時にSELF_CREATEDに設定されることを確認しており、isHubShareable()の判定は正しく機能します。

以上、よろしくお願いします。

Comment thread packages/extension/src/components/option/editor/CommandList.tsx Outdated
Co-authored-by: ujiro99 <677231+ujiro99@users.noreply.github.com>
onAutoClose was unhandled so the "shown once" flag never persisted
when the toast timed out, letting it reappear on the next command.
Also adds unit tests for isHubShareable, HubShareToast, and the
new-command toast trigger branch in CommandList that were previously
uncovered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

コードレビュー

実装全体としては、既存の ReviewRequestToast のパターンを踏襲しており、isHubShareable()hubShare.ts への切り出しも ShareButton.tsx との重複を解消できていて良い設計だと思います。UserStats 型・defaultSettings.tsstorage/index.tsDEFAULTSsettings.ts の migrate 処理まで hasShownHubShareToast フィールドが一貫して配線されている点、14ロケール全てに翻訳キーが追加されている点、isHubShareable / HubShareToast / CommandList それぞれにユニットテストが追加されている点も良いです。

以下、気になった点です。

1. トーストのスワイプ/ドラッグでの手動クローズが onShown を呼ばない可能性(軽微)

packages/extension/src/components/option/HubShareToast.tsx:70-76

toast.custom() のオプションには onAutoClose(タイムアウトによる自動クローズ)しか登録されていません。sonner はデフォルトでトーストをドラッグ/スワイプして閉じる操作をサポートしており、その場合は onAutoClose ではなく onDismiss が呼ばれます(ボタンクリック時は明示的に onShown() を呼んでいるので問題ありませんが、ドラッグで閉じられた場合はどちらのコールバックも発火しません)。3番目のコミット(ffe40a0)で「タイムアウト時に hasShownHubShareToast が永続化されない」問題を修正した意図を踏まえると、onDismiss も同様に処理しておかないと、ドラッグで閉じたユーザーには次のコマンド作成時にもう一度トーストが表示されてしまいます。オプションページでの操作なので発生頻度は低いと思いますが、onDismiss: () => onShown() も追加しておくと意図通り「一度だけ表示」を保証できそうです。

2. Settings.update のエラーハンドリングが他箇所と非対称(軽微)

packages/extension/src/components/option/editor/CommandList.tsx:253-255

showHubShareToast(data as SelectionCommand, () => {
  Settings.update("hasShownHubShareToast", () => true)
})

同じファイル内の handleUpdateCommandIdCommandList.tsx:317-321)では Settings.updateCommandId(...).catch(err => console.error(...)) としてエラーを捕捉していますが、ここでは .catch が無いため、ストレージ書き込みに失敗した場合(quota超過やコンテキスト無効化など)に未処理の Promise rejection になります。他の hubShare.ts 内の関数(shareCommandToHub など)も内部で .catch してログ出力しているので、一貫性のためにここにも .catch を付けておくと良さそうです。

3. 連続で新規コマンドを作成した場合の軽微な競合(優先度低)

packages/extension/src/components/option/editor/CommandList.tsx:246-257

commandArray.append(data) の直後に enhancedSettings.getSection(CACHE_SECTIONS.USER_STATS) を非同期で読み取り、hasShownHubShareToast を判定しています。1つ目のトーストの onShown(フラグ永続化)が完了する前に2つ目のコマンドが作成されると、両方の getSection 呼び出しが hasShownHubShareToast: false を返し、トーストが二重に表示され得ます。実運用上フォームを連続送信するケースは稀だと思われるので優先度は低いですが、念のため共有します。

その他

  • ShareButton.tsx の判定ロジックの isHubShareable() への切り出しは既存の分岐条件(IS_SUPPORT_BUILD によるバイパスを含む)を正しく保持できています。
  • テストケース(HST-01HST-06, CL-01CL-04, HS-01HS-10)は主要な分岐を網羅していて良いと思います。

全体として実装の質は高く、上記はいずれも軽微な指摘です。

@ujiro99
ujiro99 changed the base branch from main to dev-1.1.1 August 8, 2026 07:39
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

コードレビュー結果

🔴 重大: ShareButton.tsx に構文エラーが混入しています

packages/extension/src/components/option/ShareButton.tsx:44-48

    // Disable the button immediately to prevent duplicate shares/signup
    // tabs from rapid repeated clicks while the 
    
    lookup below runs.
    setStatus("pending")

既存コメント // tabs from rapid repeated clicks while the storage lookup below runs. の一部(storage)が失われ、残った lookup below runs.// の付かない生のコードとして残ってしまっています。lookup below runs.setStatus("pending") は識別子が3つ連続する不正な式であり、TypeScript/JSの構文エラーになります

ShareButton.tsxCommandTreeRenderer.tsx からインポートされてオプションページのビルドに含まれるため、これは yarn build / yarn lint を失敗させるはずです。PR概要のテスト項目には「yarn build が通ること」「yarn lint でエラーがないこと」がチェック済みとありますが、実際のマージ済みコードとは矛盾しています。ローカル環境の差異か、コミット漏れの可能性があるので確認をお願いします。

修正案:

    // Disable the button immediately to prevent duplicate shares/signup
    // tabs from rapid repeated clicks while the storage lookup below runs.
    setStatus("pending")

🟡 中: CommandList.tsx のPromiseチェーンにエラーハンドリングがない

packages/extension/src/components/option/editor/CommandList.tsx:246-257

enhancedSettings
  .getSection(CACHE_SECTIONS.USER_STATS)
  .then((userStats) => {
    if (
      !userStats.hasShownHubShareToast &&
      isHubShareable(data as SelectionCommand)
    ) {
      showHubShareToast(data as SelectionCommand, () => {
        Settings.update("hasShownHubShareToast", () => true)
      })
    }
  })

.catch() が無いため、chrome.storage へのアクセスが失敗した場合(Service Worker再起動時のcontext invalidationなど)に未処理のPromise rejectionとなります。他の箇所(hubShare.tsshareCommandToHub等)では.catch()でログ出力する方針が徹底されているので、ここも合わせておくと良さそうです。

また、連続で素早く2件のコマンドを作成した場合、非同期のgetSectionが解決する前に2回目の作成が走ると、hasShownHubShareToastのフラグ更新がまだ反映されておらずToastが2回表示される可能性があります(発生頻度は低いですが念のため)。

🟢 軽微: 無関係な整形差分

packages/extension/src/services/settings/enhancedSettings.ts:47-1131 あたりで、インデントスタイルの変更(Prettierのバージョン差異と思われる)がロジック変更と混在しています。機能追加のdiffと整形のみの変更が混ざるとレビューがしづらくなるため、可能であれば分離をおすすめします。

良い点

  • ShareButton.tsxにあった共有可否判定ロジックをisHubShareable()としてhubShare.tsに切り出し、Toast側と共通化した設計は適切です。既存のhasShownReviewRequestと同じ配線パターンを踏襲しており、一貫性があります。
  • HubShareToast.test.tsx(HST-01〜06)、CommandList.hubShareToast.test.tsx(CL-01〜04)、hubShare.test.ts(HS-01〜10)と、正常系・異常系・サポートビルド時の挙動まで幅広くテストが追加されており、テストカバレッジは良好です。
  • 全14ロケールにhub_share_toast_*キーが漏れなく追加されている点も確認できました。
  • セキュリティ面: 新規追加コードでユーザー入力を直接DOMに埋め込む処理はなく、XSS等のリスクは見当たりませんでした。

まずは ShareButton.tsx の構文エラーの修正をお願いします。それ以外は概ね良い実装だと思います。

ujiro99 and others added 3 commits August 8, 2026 17:12
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nimation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oast.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

レビュー結果

PR #444(コマンド新規作成時にHub共有Toastを表示する機能)を確認しました。全体的に既存のReviewRequestToastのパターンを踏襲した実装で、テストも手厚く追加されており良い実装だと思います。いくつか気になった点を挙げます。

バグの可能性

1. HubShareToastの「共有」ボタンが、実際に共有が成功したかを確認せずにアナリティクスを送信し、Toastを二度と表示しないフラグを立ててしまう

packages/extension/src/components/option/HubShareToast.tsx:68-79

onClick={() => {
  shareCommandToHub(command)
  sendEvent(
    ANALYTICS_EVENTS.COMMAND_SHARE,
    { event_label: "hub-share-toast" },
    SCREEN.OPTION,
  )
  toast.dismiss(toastId)
  onShown()
}}

shareCommandToHub()はIPC送信を投げっぱなしにする関数で、実際の共有結果を待ちません。特にHubに未登録(未サインイン)のユーザーの場合、バックグラウンド側(packages/extension/src/services/hub/background.ts:85-97)はサインアップページを開くだけで、共有は行われません。

一方、既存のShareButton.tsxpackages/extension/src/components/option/ShareButton.tsx:63-73)では、shareCommandToHub()呼び出し後にLOCAL_STORAGE_KEY.HUB_REGISTEREDを確認し、未登録の場合はCOMMAND_SHAREイベントを送信しない、というガードがコメント付きで実装されています。

// Users who have never signed in to the hub are redirected to the sign-up page instead...nothing is actually shared yet, so leave the button idle and skip the share analytics event for this case.

HubShareToast側にはこの確認がないため、

  • 未登録ユーザーがToastの「共有」ボタンを押すとサインアップページが開くだけなのに、command_shareイベントが実際に共有されたかのように送信されてしまう(アナリティクスの水増し)
  • onShown()が呼ばれてしまい、以後そのユーザーには二度とこのToastが表示されなくなる(実際には一度も共有できていないのに)

という実害があります。shareCommandToHubfalseを返すケース(toSubmitCommandInputが失敗する未対応コマンド種別など)でも同様に、失敗を無視してイベント送信・Toast消去が行われます。ShareButton.tsxと同様の登録チェック・戻り値チェックを入れるべきだと思います。

テスト(HubShareToast.test.tsxのHST-05)もこの「常に成功扱い」の挙動をそのまま検証しているため、現状は意図した仕様なのか見落としなのか分かりにくくなっています。

軽微な懸念点(レースコンディション)

2. 短時間に複数回コマンドを新規作成すると、Toastが重複して表示され得る

packages/extension/src/components/option/editor/CommandList.tsx:246-263

enhancedSettings
  .getSection(CACHE_SECTIONS.USER_STATS)
  .then((userStats) => {
    if (!userStats.hasShownHubShareToast && isHubShareable(data as SelectionCommand)) {
      showHubShareToast(data as SelectionCommand, () => {
        Settings.update("hasShownHubShareToast", () => true)
      })
    }
  })

hasShownHubShareToastフラグは、Toastが実際に閉じられた時点(onShownコールバック)で初めて永続化されます。showHubShareToast側には表示までの2秒の遅延(SHOW_DELAY_MS)+最大60秒の表示時間があるため、その間(最大62秒程度)に別の新規コマンドを作成すると、2回目のcommandUpsertが読むhasShownHubShareToastはまだfalseのままで、Toastが二重に表示される可能性があります。重大な不具合ではありませんが、UXとしてToastが積み重なって表示されるのは気になる点です。フラグ読み込み直後に一時的にせよ楽観的ロック(例えばモジュールスコープのフラグ)を設けるなどの対策が考えられます。

良かった点

  • ShareButton.tsxにあった共有可否判定ロジックをisHubShareable()としてhubShare.tsに切り出した点(packages/extension/src/services/hubShare.ts:1052-1062)は、ロジックの重複を排除できておりリファクタリングとして適切です。既存の判定条件と完全に一致していることを確認しました。
  • 当初onAutoClose未対応でタイムアウト時にフラグが永続化されない不具合があったのを、後続コミットで修正し、テスト(HST-06)も追加している点は良い対応です。
  • 14ロケール全てに翻訳キーが漏れなく追加されていることを確認しました。
  • 新規コマンドのidCommandEditDialog.tsx:337で常にgenerateId()(UUIDv7)が割り当てられるため、ShareButtonにあるisUUIDv7によるID再採番ロジックがHubShareToast側に無くても、このユースケースでは問題にならないことも確認済みです。

テストカバレッジ

HubShareToast.test.tsxCommandList.hubShareToast.test.tsxともに主要な分岐(表示条件、Later/Shareボタン、onAutoClose)を丁寧にカバーしており良好です。上記1.の「未登録ユーザーが共有ボタンを押した場合」のケースが追加されると、この不具合の再発防止にもなると思います。

パフォーマンス・セキュリティ

  • パフォーマンス上の懸念は特にありません(enhancedSettings.getSectionはキャッシュ経由のため軽量です)。
  • セキュリティ上の懸念も見当たりません(ユーザー入力を直接HTMLに埋め込む処理はなく、XSS等のリスクは無いと判断しました)。

🤖 このレビューはClaude Codeによって生成されました。

@ujiro99
ujiro99 merged commit 56bfd3e into dev-1.1.1 Aug 8, 2026
2 checks passed
@ujiro99
ujiro99 deleted the feat/hub-share-toast branch August 9, 2026 23:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

コマンド新規作成時、Hubへの共有を誘うToastを表示する

2 participants