mcpField
R
recovery-app-onboarding

recovery-app-onboarding

github.com/curiositechデザイン・UI257130 回インストール2 日前に更新
リポジトリ

recovery-app-onboarding について

リカバリーやウェルネスアプリのチーム向けに、ユーザーの状況を尊重し、価値を先に示し、非審判的な言葉を使うオンボーディング設計スキルです。段階的な案内、危機支援リソース、プライバシー説明、アクセシビリティ、テストを扱います。

主な機能

  1. 01思いやりを重視した導入設計
  2. 02段階的な機能紹介
  3. 03状況に応じた権限リクエスト
  4. 04危機支援と安全計画
  5. 05アクセシビリティとテスト項目

活用シーン

  1. 01初回利用フローや機能ツアーの設計
  2. 02権限要求とプライバシー説明の計画
  3. 03回復プログラム選択と安全計画の導入
  4. 04オンボーディングのアクセシビリティ確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/curiositech/some_claude_skills --skill recovery-app-onboarding

インストール後、Claude Code で/recovery-app-onboardingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
example_asset.txt
references/
api_reference.md
scripts/
example.py

詳細

回復アプリの優れたオンボーディング

尊厳と実用性をもって、支援を必要とする人々に寄り添う、思いやりのある効果的な回復・ウェルネスアプリのオンボーディング体験を構築します。

使用する場面

✅ このスキルを使用する場面:

  • 初回ユーザー向けのオンボーディングフロー
  • 機能の紹介やアプリツアー
  • 段階的開示の設計
  • 権限リクエストのタイミングと伝え方
  • ウェルカム画面と価値提案
  • 回復プログラムの選択フロー
  • 危機支援リソースの統合
  • プライバシーと匿名性に関する説明

❌ 以下の場面では使用しないでください:

  • 一般的なモバイルのレスポンシブデザイン → mobile-ux-optimizer を使用
  • マーケティング/コンバージョンの最適化 → seo-visibility-expert を使用
  • ネイティブ iOS/Android 開発 → プラットフォーム固有のスキルを使用
  • データベーススキーマの設計 → supabase-admin を使用

基本原則

1. 機能より思いやりを優先

回復に取り組むユーザーは、心身が不安定な状態にあることが少なくありません。オンボーディングに関するすべての判断で、以下を考慮する必要があります。

❌ ANTI-PATTERN: "Sign up to track your progress!"
✅ CORRECT: "You're taking a brave step. Let's set up a private space for your journey."

❌ ANTI-PATTERN: Requiring account creation before showing any value
✅ CORRECT: Let users explore core features anonymously, then offer accounts for persistence
2. まず価値を示し、その後に情報を求める

個人情報を求める前に、意義のある価値を示します。

WRONG ORDER:
1. Create account
2. Enter personal details
3. Grant permissions
4. Finally see the app

RIGHT ORDER:
1. Show immediate value (meeting finder, crisis resources)
2. Demonstrate app benefits
3. Offer optional account for saved data
4. Request permissions contextually
3. 否定的な判断を含まない言葉遣い

羞恥心を与えたり、つらい記憶を呼び起こしたりする言葉は避けます。

❌ "How many days since your last relapse?"
✅ "What's your current sobriety date?"

❌ "You failed to complete..."
✅ "No worries—pick up where you left off"

❌ "Are you an alcoholic or drug addict?"
✅ "Which recovery programs interest you?"

モバイルオンボーディング UX(2025 年のベストプラクティス)

段階的開示

複雑なオンボーディングを、理解しやすい段階に分けます。

// ✅ GOOD: Staged onboarding with clear progress
const ONBOARDING_STAGES = [
  { id: 'welcome', required: false },    // Emotional connection
  { id: 'programs', required: false },    // Personalization
  { id: 'features', required: false },    // Value discovery
  { id: 'preferences', required: false }, // Customization
  { id: 'safety', required: false },      // Crisis setup
];

// Each stage should be:
// - Skippable (never trap users)
// - Under 60 seconds to complete
// - Visually distinct with progress indicators
// - Reversible (can go back)
権限リクエストの事前説明(許可率が 28% 向上)

状況を説明せずに権限をリクエストしないでください。

// ❌ BAD: Immediate system permission dialog
useEffect(() => {
  requestLocationPermission();
}, []);

// ✅ GOOD: Contextual priming before system dialog
function MeetingSearchPrimer() {
  return (
    <div className="p-4 bg-blue-50 rounded-lg mb-4">
      <MapPin className="text-blue-500 mb-2" />
      <h3>Find Meetings Near You</h3>
      <p className="text-sm text-gray-600 mb-3">
        To show meetings within walking distance, we need your location.
        Your location stays on your device.
      </p>
      <button onClick={handleEnableLocation} className="btn-primary">
        Enable Location
      </button>
      <button onClick={handleSkip} className="btn-text">
        Search by ZIP instead
      </button>
    </div>
  );
}
「エバーボーディング」— 初回起動後も継続

オンボーディングは一度限りのイベントではありません。

// Feature discovery on first use of each feature
function useFeatureOnboarding(featureId: string) {
  const [hasSeenTooltip, setHasSeen] = useLocalStorage(`onboard:${featureId}`, false);

  return {
    showTooltip: !hasSeenTooltip,
    dismissTooltip: () => setHasSeen(true),
  };
}

// Example: First time using gratitude journal
function GratitudeJournal() {
  const { showTooltip, dismissTooltip } = useFeatureOnboarding('gratitude');

  return (
    <>
      {showTooltip && (
        <FeatureTooltip
          title="Daily Gratitude"
          description="Write 3 things you're grateful for. Research shows this builds resilience."
          onDismiss={dismissTooltip}
        />
      )}
      {/* Journal UI */}
    </>
  );
}
スピナーではなくスケルトンローダーを使用

危機的な状況にある回復中のユーザーには、すぐに反応を返す必要があります。

// ❌ BAD: Spinner creates anxiety
{isLoading && <Loader2 className="animate-spin" />}

// ✅ GOOD: Skeleton shows structure immediately
{isLoading && <MeetingCardSkeleton count={5} />}

// Skeleton implementation
function MeetingCardSkeleton() {
  return (
    <div className="p-4 bg-leather-800 rounded-lg animate-pulse">
      <div className="h-4 bg-leather-700 rounded w-3/4 mb-2" />
      <div className="h-3 bg-leather-700 rounded w-1/2 mb-4" />
      <div className="flex gap-2">
        <div className="h-6 w-16 bg-leather-700 rounded" />
        <div className="h-6 w-16 bg-leather-700 rounded" />
      </div>
    </div>
  );
}

回復アプリの機能カテゴリー

必須機能(最初に紹介)
  1. ミーティング検索 — 中核的な実用機能で、すぐに価値を提供
  2. 危機支援リソース — 命を守るためのリソースで、常にアクセス可能
  3. 安全計画 — 個人的なサポートネットワーク
エンゲージメント機能(優先度 2)
  1. 毎日のチェックイン — HALT の意識づけ
  2. 回復ジャーナル — 振り返りと成長
  3. 感謝の実践 — 前向きな強化
高度な機能(時間をかけて紹介)
  1. 断酒カウンター — 節目の記録
  2. コミュニティフォーラム — 仲間からのサポート
  3. 回復プラン — AI の支援によるガイダンス
  4. オンラインミーティング — バーチャルな選択肢
機能カードのパターン
interface OnboardingFeature {
  id: string;
  icon: React.ComponentType;
  title: string;
  description: string;
  highlight: string;        // Key benefit (e.g., "24/7 support available")
  color: string;            // Brand color for visual distinction
  category: 'essential' | 'engagement' | 'advanced';
}

const FEATURES: OnboardingFeature[] = [
  {
    id: 'meetings',
    icon: MapPin,
    title: 'Find Meetings Near You',
    description: 'Search thousands of AA, NA, CMA, SMART, and other recovery meetings.',
    highlight: '100,000+ meetings nationwide',
    color: 'bg-blue-500',
    category: 'essential',
  },
  {
    id: 'crisis',
    icon: Phone,
    title: 'Crisis Resources',
    description: 'One-tap access to crisis hotlines and emergency support.',
    highlight: '24/7 support available',
    color: 'bg-red-500',
    category: 'essential',
  },
  // ... more features
];

危機支援リソースの統合

重要:メンタルヘルスアプリで危機支援リソースを含むものは、わずか 45% です。回復アプリでは、これは必須です。

危機支援へのアクセスを常に表示
// Crisis resources should be accessible from EVERY screen
function Navigation() {
  return (
    <nav>
      {/* Normal nav items */}
      <CrisisButton className="fixed bottom-20 right-4" />
    </nav>
  );
}

// Crisis button design
function CrisisButton({ className }: { className?: string }) {
  return (
    <Link
      href="/crisis"
      className={cn(
        'flex items-center justify-center',
        'w-14 h-14 rounded-full',
        'bg-red-600 hover:bg-red-700',
        'shadow-lg shadow-red-600/30',
        'text-white',
        className
      )}
      aria-label="Crisis resources"
    >
      <Phone size={24} />
    </Link>
  );
}
オンボーディングの安全計画ステップ
// Include safety planning in onboarding, but make it optional
function SafetyPlanStep() {
  return (
    <div className="space-y-4">
      <h2>Your Safety Network</h2>
      <p className="text-gray-600">
        Having support contacts ready can make all the difference.
        This is optional—you can set this up anytime.
      </p>

      <EmergencyContactInput
        label="Emergency Contact"
        placeholder="Someone who can help in a crisis"
      />

      <SupportPersonInput
        label="Support Person"
        placeholder="Sponsor, therapist, or trusted friend"
      />

      <div className="flex gap-2 mt-6">
        <button className="btn-secondary flex-1" onClick={handleSkip}>
          Set Up Later
        </button>
        <button className="btn-primary flex-1" onClick={handleSave}>
          Save Contacts
        </button>
      </div>
    </div>
  );
}

回復プログラムの選択

包括的な複数選択

判断を加えずに、複数の回復の道筋を選べるようにします。

const RECOVERY_PROGRAMS = [
  { id: 'aa', name: 'Alcoholics Anonymous', short: 'AA', color: 'bg-blue-500' },
  { id: 'na', name: 'Narcotics Anonymous', short: 'NA', color: 'bg-purple-500' },
  { id: 'cma', name: 'Crystal Meth Anonymous', short: 'CMA', color: 'bg-pink-500' },
  { id: 'smart', name: 'SMART Recovery', short: 'SMART', color: 'bg-green-500' },
  { id: 'dharma', name: 'Recovery Dharma', short: 'Dharma', color: 'bg-amber-500' },
  { id: 'ha', name: 'Heroin Anonymous', short: 'HA', color: 'bg-red-500' },
  { id: 'oa', name: 'Overeaters Anonymous', short: 'OA', color: 'bg-teal-500' },
  { id: 'other', name: 'Other/Multiple', short: 'Other', color: 'bg-gray-500' },
];

// UI should allow multiple selections
function ProgramSelection({ selected, onChange }) {
  return (
    <div className="grid grid-cols-2 gap-3">
      {RECOVERY_PROGRAMS.map((program) => (
        <ProgramCard
          key={program.id}
          program={program}
          isSelected={selected.includes(program.id)}
          onToggle={() => toggleProgram(program.id)}
        />
      ))}
    </div>
  );
}

オンボーディングフローのアーキテクチャ

状態管理
interface OnboardingState {
  currentStep: number;
  completedSteps: string[];
  selectedPrograms: string[];
  meetingPreferences: {
    formats: ('in-person' | 'online' | 'hybrid')[];
    days: string[];
    times: ('morning' | 'afternoon' | 'evening')[];
  };
  safetyContacts: {
    emergency?: string;
    support?: string;
  };
  skippedSteps: string[];
}

// Persist across sessions
function useOnboardingState() {
  return useLocalStorage<OnboardingState>('onboarding', defaultState);
}
ステップの移動
function OnboardingWizard() {
  const [state, setState] = useOnboardingState();
  const currentStep = STEPS[state.currentStep];

  const goNext = () => {
    setState(prev => ({
      ...prev,
      currentStep: Math.min(prev.currentStep + 1, STEPS.length - 1),
      completedSteps: [...prev.completedSteps, currentStep.id],
    }));
  };

  const goBack = () => {
    setState(prev => ({
      ...prev,
      currentStep: Math.max(prev.currentStep - 1, 0),
    }));
  };

  const skip = () => {
    setState(prev => ({
      ...prev,
      skippedSteps: [...prev.skippedSteps, currentStep.id],
    }));
    goNext();
  };

  return (
    <div className="min-h-screen flex flex-col">
      <ProgressIndicator
        current={state.currentStep}
        total={STEPS.length}
        completedSteps={state.completedSteps}
      />

      <main className="flex-1 p-4">
        <CurrentStepComponent {...currentStep} state={state} setState={setState} />
      </main>

      <footer className="p-4 border-t border-leather-700">
        <div className="flex gap-3">
          {state.currentStep > 0 && (
            <button onClick={goBack} className="btn-secondary flex-1">
              Back
            </button>
          )}
          {currentStep.skippable && (
            <button onClick={skip} className="btn-text">
              Skip
            </button>
          )}
          <button onClick={goNext} className="btn-primary flex-1">
            {state.currentStep === STEPS.length - 1 ? 'Get Started' : 'Continue'}
          </button>
        </div>
      </footer>
    </div>
  );
}

マイクロインタラクション

表示アニメーション
// Staggered entrance for lists
function StaggeredList({ items, renderItem }) {
  return (
    <div className="space-y-3">
      {items.map((item, index) => (
        <div
          key={item.id}
          className="animate-fade-in-up"
          style={{ animationDelay: `${index * 100}ms` }}
        >
          {renderItem(item, index)}
        </div>
      ))}
    </div>
  );
}
カルーセルのナビゲーション
// Swipe gesture support for mobile feature tours
function useSwipeNavigation({ onNext, onPrev, threshold = 50 }) {
  const [touchStart, setTouchStart] = useState<number | null>(null);
  const [touchEnd, setTouchEnd] = useState<number | null>(null);

  const onTouchStart = (e: React.TouchEvent) => {
    setTouchEnd(null);
    setTouchStart(e.targetTouches[0].clientX);
  };

  const onTouchMove = (e: React.TouchEvent) => {
    setTouchEnd(e.targetTouches[0].clientX);
  };

  const onTouchEnd = () => {
    if (!touchStart || !touchEnd) return;
    const distance = touchStart - touchEnd;
    if (distance > threshold) onNext();
    if (distance < -threshold) onPrev();
  };

  return { onTouchStart, onTouchMove, onTouchEnd };
}

アクセシビリティ要件

WCAG AA 準拠
  • コントラスト:18px 未満のテキストは 4.5:1、18px 以上のテキストは 3:1
  • タッチターゲット:最小 44x44px
  • フォーカスインジケーター:すべてのインタラクティブ要素に、視認可能なアウトラインを表示
  • スクリーンリーダー:aria-live を使用してステップの変更を読み上げる
モーションの軽減
@media (prefers-reduced-motion: reduce) {
  .animate-fade-in-up,
  .animate-slide-in {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

テストチェックリスト

機能テスト
  • すべてのステップを完了できる
  • すべてのステップをスキップできる
  • 戻る操作が機能する
  • セッションをまたいで進行状況が保持される
  • モバイルでスワイプジェスチャーが機能する
  • キーボード操作が機能する
アクセシビリティテスト
  • スクリーンリーダーがステップの変更を読み上げる
  • すべてのインタラクティブ要素にラベルがある
  • フォーカスの順序が論理的である
  • 色のコントラストが WCAG AA に準拠している
  • タッチターゲットが最低 44x44px である
パフォーマンステスト
  • 初回読み込みが 2 秒未満
  • ステップの切り替えが 300ms 未満
  • アニメーション中にレイアウトシフトが発生しない
  • 初回読み込み後はオフラインで動作する
感情面のテスト
  • 否定的な判断を含まない言葉遣いである
  • スキップの選択肢が明確である
  • 圧力をかける手法がない
  • 危機支援リソースが表示される
  • プライバシーに関する説明が明確である

参考資料

詳しいガイドについては、/references/ をご覧ください。

  • competitor-analysis.md - I Am Sober、Nomo、Sober Grid のパターン
  • wellness-patterns.md - Headspace、Calm、Daylio から得られた知見
  • crisis-integration.md - 緊急時リソースに関するベストプラクティス

コメント