Agent Skills by ALSEL
Anthropic Claudeソフトウェア開発⭐ リポ 244品質スコア 93/100

nostr-replaceable-event-mutation-overwrite

Nostr置き換え可能イベント(Kind 0プロフィール、Kind 3コンタクト/フォローリスト、Kind 10002リレーリストなど)をクライアントアプリで変更する際の、サイレント データロスを修正します。以下の場合に使用してください:(1) ユーザーをフォローすると、フォローリスト全体が消去される、(2) プロフィールメタデータの更新で既存フィールドが失われる、(3) 新規ブラウザセッションやモバイルログインで初回操作時にデータが失われる、(4) 置き換え可能イベントの変更で古い状態やnullのキャッシュが使用される。根本原因:Nostr置き換え可能イベントは完全置き換え方式(部分更新なし)のため、古い/未読み込みキャッシュに基づいて公開すると、正規バージョンが上書きされます。React、Flutter、またはミューテーション実行時にクエリ状態が読み込まれていない可能性がある同様のリアクティブフレームワークを使用するNostrクライアントに適用されます。

description の原文を見る

Fix silent data loss when mutating Nostr replaceable events (Kind 0 profile, Kind 3 contact/follow list, Kind 10002 relay list, etc.) in client apps. Use when: (1) Following someone wipes the user's entire follow list, (2) Updating profile metadata loses existing fields, (3) Fresh browser session or mobile login causes data loss on first action, (4) Replaceable event mutation uses stale or null cached state. Root cause: Nostr replaceable events are full-replace (no partial update), so publishing based on stale/unloaded cache overwrites the canonical version. Applies to any Nostr client using React, Flutter, or similar reactive frameworks where query state may not be loaded when a mutation fires.

SKILL.md 本文

Nostr 置き換え可能イベント ミューテーション上書き

問題

Nostr の置き換え可能イベント(Kind 0、3、10002 など)は完全置換モデルを使用します: 新しいイベントを公開すると、前のイベントは完全に置き換わります。クライアントが古い、不完全、 またはnull のキャッシュ状態に基づいてミューテーションを公開した場合、リレー上の正規バージョンが サイレントに上書きされ、データロスが発生します。最も一般的なケースは、クライアントが既存の コンタクトリストを読み込む前にユーザーが誰かをフォローしたときのフォローリスト(Kind 3)の 消去です。

コンテキスト / トリガー条件

  • ユーザーからの報告「誰かをフォローしたら他のフォローが全て消えた」
  • 新規ブラウザセッションまたはモバイルログイン時のフォロー/アンフォロー アクション
  • プロフィール更新で既存メタデータフィールドが失われる
  • リレーリスト更新で既存リレーが削除される
  • UI が キャッシュ状態をミューテーション関数に渡す置き換え可能イベントに対するあらゆるミューテーション
  • React Query / TanStack Query の data がミューテーション実行時に undefined(クエリがまだ読み込み中)
  • ミューテーション関数が UI レイヤーからパラメータとして現在のイベントを受け取る

ソリューション

1. ミューテーション内で常に新鮮な状態をフェッチする

UI のキャッシュ/クエリ状態だけに依存しないでください。公開前に、ミューテーション関数内から リレーに直接アクセスして置き換え可能イベントの最新バージョンをフェッチしてください:

// 悪い例: UI キャッシュに依存(null/古い可能性あり)
mutationFn: async ({ targetPubkey, currentContactList }) => {
  const currentTags = currentContactList?.tags || []; // null -> [] -> データロス!
  // ... 新しいフォローのみで公開
}

// 良い例: ミューテーション前にリレーから新鮮な状態をフェッチ
mutationFn: async ({ targetPubkey, currentContactList }) => {
  let bestContactList = currentContactList;

  try {
    const relayEvents = await nostr.query([
      { kinds: [3], authors: [userPubkey], limit: 1 },
    ], { signal: AbortSignal.timeout(5000) });

    const relayContactList = relayEvents
      .sort((a, b) => b.created_at - a.created_at)[0] || null;

    if (relayContactList) {
      // より多くのデータを持つ方を使用してロスを防止
      const relayCount = relayContactList.tags.filter(t => t[0] === 'p').length;
      const passedCount = currentContactList?.tags.filter(t => t[0] === 'p').length ?? 0;
      if (relayCount >= passedCount) {
        bestContactList = relayContactList;
      }
    }
  } catch {
    // 渡されたコンタクトリストにフォールバック
  }

  if (!bestContactList) {
    throw new Error('既存データを読み込めません。もう一度お試しください。');
  }

  // bestContactList をミューテート...
}

2. 「最良の組み合わせ」戦略

リレーのバージョンと UI のキャッシュバージョンを比較し、より多くのデータを持つ方を使用します (より多くのタグ、より多くのフィールドなど)。これにより以下を防止します:

  • 古いリレー(UI キャッシュは最近のローカルアクションからより新しい)
  • 古い UI キャッシュ(リレーは別のクライアントからの更新を持つ)
  • null の UI キャッシュ(新規セッションでクエリが読み込まれていない)

3. 完全な失敗時は公開を拒否する

リレーフェッチも UI キャッシュもデータを提供しない場合は、空の/最小限の置き換え可能イベントを 公開する代わりにエラーをスローしてください。ユーザーフレンドリーなエラーメッセージは常に サイレントデータロスより優れています。

4. 両方向に適用する

このパターンを ALL のミューテーション方向に適用してください(フォロー AND アンフォロー、 リレー追加 AND 削除、プロフィールフィールド更新 AND クリア)。アンフォロー パスはフォロー パスと同じくらい危険です。

検証

  1. プライベート/シークレット ブラウザウィンドウでアプリを開く
  2. 複数のフォローを持つアカウントでログイン
  3. プロフィールに移動し、ページが完全に読み込まれる前に「フォロー」をタップ
  4. フォロー数が1 増加したことを確認(1 にリセットされていない)

// divine-web useFollowUser フックからの実装例
export function useFollowUser() {
  const { nostr } = useNostr();

  return useMutation({
    mutationFn: async ({ targetPubkey, currentContactList }) => {
      // ステップ 1: リレーから新鮮な状態をフェッチ
      let bestContactList = currentContactList;
      try {
        const events = await nostr.query([
          { kinds: [3], authors: [user.pubkey], limit: 1 }
        ], { signal: AbortSignal.timeout(5000) });
        const relayList = events.sort((a, b) => b.created_at - a.created_at)[0];
        if (relayList) {
          const relayFollows = relayList.tags.filter(t => t[0] === 'p').length;
          const cachedFollows = currentContactList?.tags.filter(t => t[0] === 'p').length ?? 0;
          if (relayFollows >= cachedFollows) bestContactList = relayList;
        }
      } catch { /* キャッシュにフォールバック */ }

      // ステップ 2: データがない場合は拒否
      if (!bestContactList) throw new Error('フォローリストを読み込めません');

      // ステップ 3: 安全にミューテート
      const tags = [...bestContactList.tags, ['p', targetPubkey]];
      return publishEvent({ kind: 3, tags, content: bestContactList.content });
    }
  });
}

注記

  • このパターンは ALL の Nostr 置き換え可能イベント Kind に適用されます: Kind 0(プロフィール)、 Kind 3(コンタクト)、Kind 10002(リレーリスト)、Kind 10000(ミュートリスト)、Kind 30000+(アドレス指定可能)
  • レース条件は、ネットワークが遅く、ユーザーが素早くタップするモバイルブラウザで最も一般的です
  • 確認ダイアログ(「よろしいですか?」など)は同じ古いキャッシュをチェックするため役立ちません。 修正はミューテーション自体の内部に必要です
  • リレーフェッチの5秒タイムアウトは、安全性と UX のバランスが取れています
  • 追加のセーフティとして、初期クエリの読み込み中はミューテーション ボタンを無効にする ことも検討してください

ライセンス: MPL-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ

詳細情報

作者
divinevideo
リポジトリ
divinevideo/divine-mobile
ライセンス
MPL-2.0
最終更新
2026/5/12

Source: https://github.com/divinevideo/divine-mobile / ライセンス: MPL-2.0

関連スキル

汎用ソフトウェア開発⭐ リポ 39,967

doubt-driven-development

重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 1,175

apprun-skills

TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。

by yysun
OpenAIソフトウェア開発⭐ リポ 797

desloppify

コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。

by Git-on-my-level
汎用ソフトウェア開発⭐ リポ 39,967

debugging-and-error-recovery

テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

test-driven-development

テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。

by addyosmani
汎用ソフトウェア開発⭐ リポ 39,967

incremental-implementation

変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。

by addyosmani
本サイトは GitHub 上で公開されているオープンソースの SKILL.md ファイルをクロール・インデックス化したものです。 各スキルの著作権は原作者に帰属します。掲載に問題がある場合は info@alsel.co.jp または /takedown フォームよりご連絡ください。
原作者: divinevideo · divinevideo/divine-mobile · ライセンス: MPL-2.0