From d6f89d206e51ca38568649bd2d039e643ba8a94d Mon Sep 17 00:00:00 2001 From: filemon Date: Sun, 5 Apr 2026 22:56:11 -0300 Subject: [PATCH] Remove quantity leftovers and unify cooldown UX across all item surfaces Quantity cleanup: - Remove quantity field from ResolvedInventoryItem, CompanionItem, and BlobbiShopModal's local interface - Remove all quantity: Infinity assignments from item resolution - Rename filterInventoryByAction to getItemsForAction with a cleaner signature (drop unused _storage parameter) - Update barrel export and call sites - Fix stale 'inventory' comments in blobbi-action-utils.ts Cooldown UX consistency: - Add Clock icon to dashboard ItemsTabContent (previously only showed dimmed opacity with no icon) - Normalize shop modal button variant (outline, matching action modal style) - All three item surfaces now share the same visual language: loading = spinner, cooldown = clock icon + disabled + muted --- .../components/BlobbiActionInventoryModal.tsx | 4 +-- src/blobbi/actions/index.ts | 2 +- src/blobbi/actions/lib/blobbi-action-utils.ts | 35 +++++++++---------- src/blobbi/companion/interaction/types.ts | 4 +-- .../interaction/useCompanionActionMenu.ts | 1 - .../shop/components/BlobbiShopModal.tsx | 6 ++-- src/pages/BlobbiPage.tsx | 6 ++-- 7 files changed, 25 insertions(+), 33 deletions(-) diff --git a/src/blobbi/actions/components/BlobbiActionInventoryModal.tsx b/src/blobbi/actions/components/BlobbiActionInventoryModal.tsx index d999e985..f6dfffb6 100644 --- a/src/blobbi/actions/components/BlobbiActionInventoryModal.tsx +++ b/src/blobbi/actions/components/BlobbiActionInventoryModal.tsx @@ -16,7 +16,7 @@ import type { BlobbiCompanion, BlobbonautProfile } from '@/blobbi/core/lib/blobb import { cn } from '@/lib/utils'; import { - filterInventoryByAction, + getItemsForAction, previewStatChanges, previewMedicineForEgg, previewCleanForEgg, @@ -58,7 +58,7 @@ export function BlobbiActionInventoryModal({ // Get all available items for this action from the catalog. const availableItems = useMemo(() => { - return filterInventoryByAction([], action, { stage: companion.stage }); + return getItemsForAction(action, { stage: companion.stage }); }, [action, companion.stage]); // Check stage restrictions for this specific action diff --git a/src/blobbi/actions/index.ts b/src/blobbi/actions/index.ts index d5286f9e..4e0a6d36 100644 --- a/src/blobbi/actions/index.ts +++ b/src/blobbi/actions/index.ts @@ -136,7 +136,7 @@ export { clampStat, applyStat, applyItemEffects, - filterInventoryByAction, + getItemsForAction, decrementStorageItem, canUseAction, canUseDirectAction, diff --git a/src/blobbi/actions/lib/blobbi-action-utils.ts b/src/blobbi/actions/lib/blobbi-action-utils.ts index 9fff67cb..7be866d1 100644 --- a/src/blobbi/actions/lib/blobbi-action-utils.ts +++ b/src/blobbi/actions/lib/blobbi-action-utils.ts @@ -7,18 +7,18 @@ import { getShopItemById, getLiveShopItems } from '@/blobbi/shop/lib/blobbi-shop // ─── Action Types ───────────────────────────────────────────────────────────── /** - * Actions that consume inventory items + * Item-based care actions (use a shop catalog item on the companion) */ export type InventoryAction = 'feed' | 'play' | 'clean' | 'medicine'; /** - * Non-inventory actions that don't consume items + * Direct actions that don't use items * These actions affect stats directly without using shop items. */ export type DirectAction = 'play_music' | 'sing'; /** - * All Blobbi actions (inventory + direct) + * All Blobbi actions (item-based + direct) */ export type BlobbiAction = InventoryAction | DirectAction; @@ -33,7 +33,7 @@ export const ACTION_TO_ITEM_TYPE: Record = { }; /** - * Action metadata for UI display (inventory actions) + * Action metadata for UI display (item-based care actions) */ export const ACTION_METADATA: Record = { feed: { @@ -59,7 +59,7 @@ export const ACTION_METADATA: Record = { play_music: { @@ -270,14 +270,13 @@ export function hasHappinessEffectForEgg(effects: ItemEffect | undefined): boole return effects.happiness !== undefined && effects.happiness !== 0; } -// ─── Inventory Helpers ──────────────────────────────────────────────────────── +// ─── Item Helpers ───────────────────────────────────────────────────────────── /** - * Resolved inventory item with shop metadata + * Resolved catalog item with shop metadata. */ export interface ResolvedInventoryItem { itemId: string; - quantity: number; name: string; icon: string; type: ShopItemCategory; @@ -285,7 +284,7 @@ export interface ResolvedInventoryItem { } /** - * Options for filtering inventory by action + * Options for filtering catalog items by action. */ export interface FilterInventoryOptions { /** Companion stage - used to filter items by egg-compatible effects */ @@ -294,7 +293,7 @@ export interface FilterInventoryOptions { /** * Get all available items for an action type from the shop catalog. - * Items are abilities/tools — no inventory ownership is required. + * Items are reusable abilities — no ownership is required. * * Filtering rules: * - Only items matching the action's item type are included @@ -303,8 +302,7 @@ export interface FilterInventoryOptions { * - medicine action: only items with health effect * - clean action: only items with hygiene or happiness effect */ -export function filterInventoryByAction( - _storage: StorageItem[], +export function getItemsForAction( action: InventoryAction, options: FilterInventoryOptions = {} ): ResolvedInventoryItem[] { @@ -324,16 +322,15 @@ export function filterInventoryByAction( // For eggs, filter items by egg-compatible effects if (isEgg) { if (action === 'medicine' && !hasMedicineEffectForEgg(shopItem.effect)) { - continue; // Skip medicine without health effect + continue; } if (action === 'clean' && !hasHygieneEffectForEgg(shopItem.effect) && !hasHappinessEffectForEgg(shopItem.effect)) { - continue; // Skip hygiene items without hygiene or happiness effect + continue; } } result.push({ itemId: shopItem.id, - quantity: Infinity, name: shopItem.name, icon: shopItem.icon, type: shopItem.type, @@ -374,7 +371,7 @@ export function decrementStorageItem( // ─── Stage Restriction Helpers ──────────────────────────────────────────────── /** - * Stages that can use general inventory items (food, toys, hygiene) + * Stages that can use general items (food, toys, hygiene) */ export const GENERAL_ITEM_USABLE_STAGES = ['baby', 'adult'] as const; @@ -407,14 +404,14 @@ export const EGG_VISIBLE_ACTIONS: BlobbiAction[] = ['clean', 'medicine', 'play_m export const EGG_ALLOWED_ACTIONS = EGG_ALLOWED_INVENTORY_ACTIONS; /** - * Check if a companion can use a specific inventory action. + * Check if a companion can use a specific item action. * * Note: This function no longer hard-blocks egg actions at the domain layer. * UI visibility is handled separately by `isActionVisibleForStage()`. * The domain layer allows all actions - UI chooses what to show. */ export function canUseAction(_companion: BlobbiCompanion, _action: InventoryAction): boolean { - // All stages can technically use all inventory actions at the domain layer. + // All stages can technically use all item actions at the domain layer. // UI filtering determines what actions are shown to users. return true; } @@ -440,7 +437,7 @@ export function isActionVisibleForStage(stage: 'egg' | 'baby' | 'adult', action: } /** - * Check if a companion can use general inventory items (feed, play, clean). + * Check if a companion can use general items (feed, play, clean). * Eggs cannot use food, toys, or hygiene items. * @deprecated Use canUseAction(companion, action) for action-specific checks */ diff --git a/src/blobbi/companion/interaction/types.ts b/src/blobbi/companion/interaction/types.ts index b9765ffb..83fcf1d5 100644 --- a/src/blobbi/companion/interaction/types.ts +++ b/src/blobbi/companion/interaction/types.ts @@ -63,7 +63,7 @@ export function getItemCategoryForAction(actionId: CompanionMenuAction): ShopIte /** * Normalized item representation for the companion UI. - * This is a simplified view of inventory items optimized for rendering. + * A simplified view of shop catalog items optimized for rendering. */ export interface CompanionItem { /** Unique item ID (matches shop item ID) */ @@ -74,8 +74,6 @@ export interface CompanionItem { emoji: string; /** Item category */ category: ShopItemCategory; - /** Quantity available in inventory */ - quantity: number; /** Item effects when used */ effect?: ItemEffect; } diff --git a/src/blobbi/companion/interaction/useCompanionActionMenu.ts b/src/blobbi/companion/interaction/useCompanionActionMenu.ts index 82a46204..89b4432a 100644 --- a/src/blobbi/companion/interaction/useCompanionActionMenu.ts +++ b/src/blobbi/companion/interaction/useCompanionActionMenu.ts @@ -112,7 +112,6 @@ function resolveItemsForAction( name: shopItem.name, emoji: shopItem.icon, category: shopItem.type, - quantity: Infinity, effect: shopItem.effect, }); } diff --git a/src/blobbi/shop/components/BlobbiShopModal.tsx b/src/blobbi/shop/components/BlobbiShopModal.tsx index 5dff713e..ab7722c7 100644 --- a/src/blobbi/shop/components/BlobbiShopModal.tsx +++ b/src/blobbi/shop/components/BlobbiShopModal.tsx @@ -24,10 +24,9 @@ import { cn, formatCompactNumber } from '@/lib/utils'; type TopTab = 'items' | 'shop'; -/** Resolved inventory item with shop metadata and usability info */ +/** Resolved catalog item with shop metadata and usability info */ interface ResolvedInventoryItem extends ShopItem { itemId: string; - quantity: number; canUse: boolean; reason?: string; } @@ -93,7 +92,6 @@ export function BlobbiShopModal({ result.push({ ...item, itemId: item.id, - quantity: Infinity, canUse: usability.canUse, reason: usability.reason, }); @@ -316,7 +314,7 @@ function ItemsGrid({ items, onUseItem, isUsingItem, usingItemId, onGoToShop: _on {item.canUse ? ( ); })}