0c29506402
- Extract utility functions from component files into dedicated modules to fix react-refresh/only-export-components warnings: - parseBadgeDefinition -> src/lib/parseBadgeDefinition.ts - parseProfileBadges -> src/lib/parseProfileBadges.ts - getColors, paletteToTheme -> src/lib/colorMomentUtils.ts - parseDimToAspectRatio, eventToMediaItem -> src/lib/mediaUtils.ts - isAudioUrl, isImageUrl, isVideoUrl -> src/lib/mediaTypeDetection.ts - buildKindOptions, parseSelectedKinds -> src/lib/feedFilterUtils.ts - useVideoThumbnail -> src/hooks/useVideoThumbnail.ts - useEnvelopeDimensions -> src/hooks/useEnvelopeDimensions.ts - usePortalContainer -> src/hooks/usePortalContainer.ts - useAudioPlayer -> src/contexts/audioPlayerContextDef.ts - SubHeaderBar context/hooks -> src/components/SubHeaderBarContext.ts - EmotionDev hooks -> src/blobbi/dev/useEmotionDev.ts - BlobbiActions context def -> BlobbiActionsContextDef.ts - Remove export from internal-only functions (useEventComments, parseEmojiPack, useScrollCarets, formatEffectSummary, getSortedEffectEntries) - Fix react-hooks/exhaustive-deps warnings by adding missing dependencies to useEffect/useCallback/useMemo hooks across 14 files - Fix logical expression dependency warnings by wrapping conditional values (tasks, pubkeys, authorPubkeys) in useMemo - Move module-level constants (CORE_TAB_IDS, CORE_TAB_LABELS, DEFAULT_TAB_LABELS) out of ProfilePage component body - Reorder usePushNotifications hooks so syncPreferences is defined before enable to fix block-scoped variable error
233 lines
7.7 KiB
TypeScript
233 lines
7.7 KiB
TypeScript
// src/blobbi/actions/hooks/useActiveTaskProcess.ts
|
|
|
|
/**
|
|
* Central abstraction for the active task process (hatch or evolve).
|
|
*
|
|
* This hook consolidates all scattered if/else logic for determining:
|
|
* - Which process is active (incubating vs evolving)
|
|
* - Which tasks to use (hatch vs evolve)
|
|
* - Thresholds and configuration
|
|
* - Badge-related computed values
|
|
*
|
|
* ARCHITECTURE RULES:
|
|
* - Computed tasks remain the source of truth
|
|
* - Tags are cache only for PERSISTENT tasks
|
|
* - Dynamic tasks are NEVER persisted
|
|
* - Badge counts ALL incomplete tasks (persistent + dynamic)
|
|
*/
|
|
|
|
import { useMemo } from 'react';
|
|
import type { BlobbiCompanion } from '@/blobbi/core/lib/blobbi';
|
|
import type { HatchTask, HatchTasksResult } from './useHatchTasks';
|
|
import type { EvolveTasksResult } from './useEvolveTasks';
|
|
import { HATCH_REQUIRED_INTERACTIONS } from './useHatchTasks';
|
|
import { EVOLVE_REQUIRED_INTERACTIONS } from './useEvolveTasks';
|
|
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
/** The type of task process currently active */
|
|
export type TaskProcessType = 'hatch' | 'evolve' | null;
|
|
|
|
/**
|
|
* Configuration for the active task process.
|
|
* This provides a unified interface regardless of whether
|
|
* the process is hatch or evolve.
|
|
*/
|
|
export interface TaskProcessConfig {
|
|
/** The type of process ('hatch' | 'evolve' | null) */
|
|
type: TaskProcessType;
|
|
/** Whether there is an active task process */
|
|
isActive: boolean;
|
|
/** Required interactions threshold for the current process */
|
|
interactionThreshold: number;
|
|
}
|
|
|
|
/**
|
|
* Result of the active task process hook.
|
|
* Provides unified access to all task-related state.
|
|
*/
|
|
export interface ActiveTaskProcessResult {
|
|
/** Configuration for the current process */
|
|
config: TaskProcessConfig;
|
|
|
|
/** All tasks for the current process (empty if no active process) */
|
|
tasks: HatchTask[];
|
|
/** Whether tasks are still loading */
|
|
isLoading: boolean;
|
|
/** Whether all tasks (persistent + dynamic) are complete */
|
|
allCompleted: boolean;
|
|
/** Whether all persistent tasks are complete */
|
|
persistentTasksComplete: boolean;
|
|
/** Whether the dynamic task is complete */
|
|
dynamicTaskComplete: boolean;
|
|
|
|
/** Refetch function for current tasks */
|
|
refetch: () => void;
|
|
|
|
// ─── Badge-related computed values ───
|
|
|
|
/**
|
|
* Count of ALL remaining incomplete tasks (persistent + dynamic).
|
|
* This is used for the badge display.
|
|
* Dynamic tasks ARE counted here but are NEVER synced to tags.
|
|
*/
|
|
remainingTasksCount: number;
|
|
|
|
/**
|
|
* Only persistent tasks that are incomplete.
|
|
* Used for sync logic - dynamic tasks must NEVER be synced.
|
|
*/
|
|
incompletePersistentTasks: HatchTask[];
|
|
|
|
/**
|
|
* Only persistent tasks that are complete.
|
|
* Used for sync logic.
|
|
*/
|
|
completedPersistentTasks: HatchTask[];
|
|
|
|
/**
|
|
* Stable string key of completed persistent task IDs.
|
|
* Used for sync anti-loop protection.
|
|
*/
|
|
completedPersistentTaskIds: string;
|
|
|
|
/**
|
|
* Tasks to sync (persistent only, with completion status).
|
|
* Dynamic tasks are excluded.
|
|
*/
|
|
tasksToSync: Array<{ taskId: string; completed: boolean }>;
|
|
}
|
|
|
|
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Filter tasks to only persistent tasks.
|
|
* Dynamic tasks must NEVER be synced to tags.
|
|
*/
|
|
export function filterPersistentTasks(tasks: HatchTask[]): HatchTask[] {
|
|
return tasks.filter(t => t.type === 'persistent');
|
|
}
|
|
|
|
/**
|
|
* Filter tasks to only dynamic tasks.
|
|
*/
|
|
export function filterDynamicTasks(tasks: HatchTask[]): HatchTask[] {
|
|
return tasks.filter(t => t.type === 'dynamic');
|
|
}
|
|
|
|
// ─── Main Hook ────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Hook that provides a unified interface for the active task process.
|
|
*
|
|
* Usage:
|
|
* ```ts
|
|
* const taskProcess = useActiveTaskProcess(companion, hatchTasks, evolveTasks);
|
|
*
|
|
* // Access unified data
|
|
* taskProcess.config.type // 'hatch' | 'evolve' | null
|
|
* taskProcess.tasks // current tasks
|
|
* taskProcess.remainingTasksCount // for badge (includes dynamic)
|
|
* taskProcess.tasksToSync // for sync (excludes dynamic)
|
|
* ```
|
|
*/
|
|
export function useActiveTaskProcess(
|
|
companion: BlobbiCompanion | null,
|
|
hatchTasks: HatchTasksResult,
|
|
evolveTasks: EvolveTasksResult
|
|
): ActiveTaskProcessResult {
|
|
// Determine which process is active
|
|
const processType = useMemo((): TaskProcessType => {
|
|
if (!companion) return null;
|
|
if (companion.state === 'incubating') return 'hatch';
|
|
if (companion.state === 'evolving') return 'evolve';
|
|
return null;
|
|
}, [companion]);
|
|
|
|
// Build configuration
|
|
const config = useMemo((): TaskProcessConfig => {
|
|
const isActive = processType !== null;
|
|
const interactionThreshold = processType === 'hatch'
|
|
? HATCH_REQUIRED_INTERACTIONS
|
|
: processType === 'evolve'
|
|
? EVOLVE_REQUIRED_INTERACTIONS
|
|
: 0;
|
|
|
|
return {
|
|
type: processType,
|
|
isActive,
|
|
interactionThreshold,
|
|
};
|
|
}, [processType]);
|
|
|
|
// Get the active tasks result based on process type
|
|
const activeResult = useMemo(() => {
|
|
if (processType === 'hatch') return hatchTasks;
|
|
if (processType === 'evolve') return evolveTasks;
|
|
return null;
|
|
}, [processType, hatchTasks, evolveTasks]);
|
|
|
|
// Extract tasks and state from active result
|
|
const tasks = useMemo(() => activeResult?.tasks ?? [], [activeResult]);
|
|
const isLoading = activeResult?.isLoading ?? false;
|
|
const allCompleted = activeResult?.allCompleted ?? false;
|
|
const persistentTasksComplete = activeResult?.persistentTasksComplete ?? false;
|
|
const dynamicTaskComplete = activeResult?.dynamicTaskComplete ?? false;
|
|
const refetch = activeResult?.refetch ?? (() => {});
|
|
|
|
// Compute persistent task list (dynamic tasks computed for badge count directly from tasks array)
|
|
const persistentTasks = useMemo(() => filterPersistentTasks(tasks), [tasks]);
|
|
|
|
// Compute incomplete tasks (for badge - includes BOTH persistent and dynamic)
|
|
const remainingTasksCount = useMemo(() => {
|
|
// Count ALL incomplete tasks - persistent AND dynamic
|
|
// Dynamic tasks are included in badge count but NEVER synced to tags
|
|
return tasks.filter(t => !t.completed).length;
|
|
}, [tasks]);
|
|
|
|
// Compute persistent task lists for sync
|
|
const incompletePersistentTasks = useMemo(() =>
|
|
persistentTasks.filter(t => !t.completed),
|
|
[persistentTasks]
|
|
);
|
|
|
|
const completedPersistentTasks = useMemo(() =>
|
|
persistentTasks.filter(t => t.completed),
|
|
[persistentTasks]
|
|
);
|
|
|
|
// Compute stable string key for completed persistent tasks (anti-loop)
|
|
const completedPersistentTaskIds = useMemo(() => {
|
|
if (!completedPersistentTasks.length) return '';
|
|
return completedPersistentTasks
|
|
.map(t => t.id)
|
|
.sort()
|
|
.join(',');
|
|
}, [completedPersistentTasks]);
|
|
|
|
// Compute tasks to sync (persistent only)
|
|
// CRITICAL: Dynamic tasks must NEVER be included here
|
|
const tasksToSync = useMemo(() => {
|
|
if (!persistentTasks.length) return [];
|
|
return persistentTasks.map(t => ({
|
|
taskId: t.id,
|
|
completed: t.completed,
|
|
}));
|
|
}, [persistentTasks]);
|
|
|
|
return {
|
|
config,
|
|
tasks,
|
|
isLoading,
|
|
allCompleted,
|
|
persistentTasksComplete,
|
|
dynamicTaskComplete,
|
|
refetch,
|
|
remainingTasksCount,
|
|
incompletePersistentTasks,
|
|
completedPersistentTasks,
|
|
completedPersistentTaskIds,
|
|
tasksToSync,
|
|
};
|
|
}
|