Skip to content

Refine billing, settings, rewards, and usage UI#547

Merged
senamakel merged 25 commits intotinyhumansai:mainfrom
senamakel:fix/monday-3
Apr 14, 2026
Merged

Refine billing, settings, rewards, and usage UI#547
senamakel merged 25 commits intotinyhumansai:mainfrom
senamakel:fix/monday-3

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented Apr 14, 2026

Summary

  • refresh billing surfaces with clearer plan, payment, and history presentation across settings
  • reorganize settings into section-based navigation and add reusable page back navigation
  • expand rewards and intelligence UI with new tabs and supporting components
  • improve skills and composio metadata handling, plus related configuration updates
  • add usage-state coverage for budget completion messaging and ship formatter follow-up fixes required by the repo hook

Problem

  • billing, rewards, intelligence, and settings flows had grown inconsistent and harder to navigate
  • usage messaging around budget completion needed clearer coverage
  • several related UI areas were being updated together, but the branch needed to land as one coherent pass

Solution

  • split larger pages into dedicated tab and panel components, then wire the routes and navigation around those units
  • update billing and rewards UI copy/layout to make plans, referrals, payments, and community actions easier to scan
  • add the budget completion message logic test coverage and supporting API/config tweaks used by the updated UI
  • include the formatter-only follow-up commit produced by the pre-push hook so the branch stays pushable

Submission Checklist

  • Unit tests — added/updated Vitest coverage in app/src/hooks/useUsageState.test.ts and app/src/services/api/__tests__/billingApi.test.ts
  • E2E / integration — not run for this branch before PR creation
  • N/A — full E2E was not run while creating this PR; no claim of additional integration coverage
  • Doc comments — no new public API doc comment pass was added in this branch
  • Inline comments — no additional inline comment pass was added in this branch

Impact

  • desktop React/Tauri app settings, billing, rewards, skills, and intelligence screens change behavior and layout
  • no explicit migration noted; lint still reports five existing React hook warnings outside the files changed for the formatter follow-up commit
  • pre-push checks completed successfully on push: format, lint, TypeScript compile, and Tauri Rust check

Related

  • Issue(s): none linked
  • Follow-up PR(s)/TODOs: consider splitting future UI refactors into smaller topical PRs and addressing the existing hook warnings separately

Summary by CodeRabbit

  • New Features

    • Intelligence: Memory, Subconscious, and Dreams tabs
    • Rewards: Referrals, Redeem, and Community tabs
    • Settings Billing: Plans, Payments, and History tabs
    • New pill-style tab component and category icons across the app
  • Improvements

    • Staging/production environment selector and backend URL update for staging
    • Redesigned top-up/redeem/referral UIs (simpler apply flow, copy/share)
    • Updated subscription pricing and plan text
    • Clearer budget completion and rate-limit messaging

- Added `react-icons` dependency to the project for enhanced icon usage.
- Introduced new skill icons in the `toolkitMeta.tsx` component, replacing SVG icons with `react-icons` for improved maintainability and consistency.
- Created a new `skillCategories.ts` file to define skill categories and their order, streamlining the management of skill categories across the application.
- Refactored the `SkillCategoryFilter` component to utilize the new skill categories structure, enhancing clarity and reducing redundancy in the codebase.
- Updated the `Skills` page to leverage the new icon rendering method and skill categories, improving the overall user experience.
- Added tests to ensure the correct functionality of the new skill category and icon implementations.
- Updated `.env.example` and `app/.env.example` to reflect new backend URL and added optional environment selector for staging.
- Enhanced `SettingsHome` component by separating account and billing sections for improved clarity.
- Introduced a new billing section in the settings menu to streamline user navigation.
- Updated `useSettingsNavigation` hook to accommodate changes in settings structure.
- Improved `BillingPanel` to handle session token checks and ensure accurate billing state retrieval.
- Refactored `Rewards` page to enhance user experience with clearer progress indicators and improved layout.
- Added tests to validate changes in the rewards and settings components.
…tion

- Introduced a new staging API base URL constant for better environment management.
- Added app environment variable constants to streamline environment detection.
- Refactored effective API URL resolution to accommodate environment-specific defaults.
- Implemented functions to resolve app environment from process environment variables.
- Added tests to validate the correct behavior of staging and production API URL handling.
- Introduced `RewardsCommunityTab` and `RewardsReferralsTab` components to enhance the rewards management interface.
- The `RewardsCommunityTab` displays user progress, Discord role statuses, and connection options, improving user engagement with rewards.
- The `RewardsReferralsTab` allows users to manage their referral program, track progress, and access coupon rewards in a streamlined layout.
- Updated the `Rewards` page to integrate these new tabs, enhancing overall user experience and navigation.
…components

- Added `ReferralRewardsSection` to manage referral statistics and code application, enhancing user engagement with the referral program.
- Created `RewardsRedeemTab` to streamline the process of applying reward codes, improving the overall rewards management experience.
- Updated `Rewards` page to include the new redeem tab, allowing users to easily switch between referral and redeem functionalities.
- Refactored `RewardsCommunityTab` to adjust the referral selection handler, ensuring consistent navigation across the rewards interface.
- Removed redundant UI elements in `RewardsCouponSection` for a cleaner layout.
- Enhanced tests to cover new functionalities and ensure robust performance across the rewards system.
…lter and Rewards pages

- Added a new `PillTabBar` component to enhance tab navigation with customizable styles and item rendering.
- Refactored `SkillCategoryFilter` to utilize `PillTabBar`, improving code clarity and reducing redundancy in button rendering.
- Updated the `Rewards` page to replace the existing tab navigation with `PillTabBar`, streamlining the user interface for switching between referral, rewards, and redeem tabs.
- Enhanced the `ReferralRewardsSection` layout for better user experience and consistency across the rewards management interface.
- Improved tests to cover the new `PillTabBar` functionality and ensure robust performance across the updated components.
- Changed the input placeholder from "Promo code" to "Coupon code" for clarity.
- Updated button text from "Apply code" to "Redeem Code" to better reflect the action being performed.
- Adjusted loading state text from "Applying…" to "Redeeming..." for consistency in user feedback.
- Updated the `ComposioConnectModal` to simplify the connection message, removing unnecessary wording for clarity.
- Introduced a `TOOLKIT_ALIASES` mapping in `toolkitMeta.tsx` to standardize toolkit slugs, improving consistency across the application.
- Refactored the `composioToolkitMeta` function to utilize the new slug mapping, enhancing toolkit metadata retrieval.
- Improved the `useComposioIntegrations` hook to leverage the canonicalized toolkit slugs for better integration handling.
- Adjusted the `Skills` page to normalize toolkit slugs during rendering, ensuring a consistent user experience.
- Updated tests to reflect changes in messaging and toolkit handling, ensuring robust functionality across the application.
… features

- Introduced `IntelligenceDreamsTab`, which displays generated dreams based on daily life events, enhancing user engagement with a visually appealing layout.
- Added `IntelligenceMemoryTab` to manage actionable items, featuring search and filter capabilities for improved user interaction with memory data.
- Created `IntelligenceSubconsciousTab` to handle subconscious tasks and logs, providing users with insights and management options for their subconscious activities.
- Refactored the `RewardsCommunityTab` to remove unused Discord role status logic, streamlining the component for better performance.
- Implemented a new `PageBackButton` component for consistent navigation across settings pages.
- Enhanced the `BillingPanel` and its subcomponents to improve user experience in managing billing and subscription details, including transaction history and payment methods.
- Added new billing-related tabs for better organization and access to billing features, including `BillingOverviewTab`, `BillingPaymentsTab`, and `BillingPlansTab`.
- Updated tests to ensure functionality across new and refactored components, maintaining robust application performance.
…ed user experience

- Changed the default selected tab in `BillingPanel` from 'overview' to 'plans' to prioritize subscription management.
- Removed the `BillingOverviewTab` from the `BillingPanel`, streamlining the billing interface.
- Enhanced the `BillingPlansTab` header to clarify the purpose, changing "Explore tiers" to "Choose a Subscription Plan".
- Updated the description in `BillingPlansTab` for better clarity on payment options.
- Improved the layout of the `SubscriptionPlans` component to better highlight crypto payment options and their availability.
- Cleaned up unused code and comments for better maintainability.
…ents

- Removed unused `teamUsage` state and related API call from `BillingPanel` to simplify data management.
- Adjusted layout in `BillingPlansTab` for improved visual hierarchy and user experience.
- Cleaned up code by eliminating unnecessary comments and enhancing maintainability.
…onsiveness

- Updated the layout of the `SubscriptionPlans` component to ensure better responsiveness and visual consistency.
- Adjusted class names to include minimum height and width properties for better alignment across different screen sizes.
- Enhanced the layout of the pricing display section for improved clarity and user experience.
…or improved clarity and user experience

- Adjusted pricing for BASIC and PRO plans to reflect new monthly and annual rates.
- Enhanced feature descriptions for subscription plans to better communicate value.
- Removed redundant UI elements in the BillingPaymentsTab and PayAsYouGoCard for a cleaner layout.
- Added loading and confirmation messages in SubscriptionPlans to improve user feedback during payment processes.
- Updated class names for better responsiveness and visual consistency across billing components.
…ing components

- Updated error message in BillingPanel to specify adding a payment card on Stripe for clarity.
- Changed header text in AutoRechargeSection to "Enable Auto-Recharge" for better user understanding.
- Modified button text in AutoRechargeSection to "Add card on Stripe" for consistency.
- Enhanced styling in PayAsYouGoCard for improved visual appeal and user interaction.
- Removed redundant UI elements in PayAsYouGoCard for a cleaner layout.
…tency across billing components

- Eliminated `setArError` prop from BillingPanel, AutoRechargeSection, and BillingPaymentsTab to streamline error handling.
- Enhanced layout and styling in BillingHistoryTab and SubscriptionPlans for better visual consistency and user experience.
- Removed the BillingOverviewTab component to simplify the billing interface and improve maintainability.
…onPlans display

- Revised feature descriptions in the PLANS array for clarity and conciseness.
- Increased the number of displayed features in the SubscriptionPlans component to provide users with more information at a glance.
… functionality

- Revised feature descriptions in the PLANS array to reflect increased usage limits.
- Renamed header in BillingHistoryTab to "Transaction History" and updated description for clarity.
- Adjusted transaction amount formatting in BillingHistoryTab to display five decimal places.
- Removed redundant UI elements in BillingPaymentsTab to streamline the layout.
- Enhanced PayAsYouGoCard with improved credit balance display and top-up options, including validation for custom amounts.
- Introduced `shouldShowBudgetCompletedMessage` to the `UsageState` interface to indicate when the budget completion message should be displayed.
- Updated the `useUsageState` hook to calculate the budget completion message based on user credits and budget status.
- Enhanced tests for `useUsageState` to verify the correct behavior of the budget completion message under various scenarios.
- Modified the `Conversations` component to display the budget completion message appropriately based on the new logic.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 14, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds env-aware backend selection and staging defaults, centralizes app-root/workspace resolution with a staging suffix, introduces a reusable PillTabBar and extracts multiple tab components (Intelligence, Rewards, Billing), centralizes skill/category icons, and updates billing/credits normalization and related UI/tests.

Changes

Cohort / File(s) Summary
Env / Backend config
/.env.example, app/.env.example, app/src/utils/config.ts, app/src/vite-env.d.ts, src/api/config.rs
Add OPENHUMAN_APP_ENV / VITE_OPENHUMAN_APP_ENV, prefer VITE_BACKEND_URL or compute DEFAULT_BACKEND_URL based on APP_ENV (staging vs production), and update example backend URLs to staging-api.tinyhumans.ai.
Root dir / workspace resolution
src/openhuman/config/schema/load.rs, src/openhuman/config/schema/types.rs, src/openhuman/config/ops.rs, src/openhuman/encryption/core.rs, src/openhuman/memory/*, src/openhuman/agent/harness/definition_loader.rs
Centralize root via default_root_openhuman_dir() and derive env-scoped root name (.openhuman-staging when staging); replace direct dirs::home_dir() usage across multiple modules.
Generic tab component
app/src/components/PillTabBar.tsx
Add a typed, accessible pill-style tab component (controlled selected + onChange, customizable render/classes).
Intelligence tabs extraction
app/src/pages/Intelligence.tsx, app/src/components/intelligence/*
Replace inline Intelligence UI with PillTabBar and new tab components: IntelligenceMemoryTab, IntelligenceSubconsciousTab, IntelligenceDreamsTab.
Rewards tab extraction
app/src/pages/Rewards.tsx, app/src/components/rewards/*
Convert Rewards page to tabbed UI using PillTabBar; add RewardsCommunityTab, RewardsReferralsTab, RewardsRedeemTab and move rendering into those components.
Billing refactor into tabs
app/src/pages/Settings.tsx, app/src/components/settings/panels/BillingPanel.tsx, .../billing/*
Refactor Billing into Plans/Payments/History tabs using PillTabBar; add BillingPlansTab, BillingPaymentsTab, BillingHistoryTab, adjust transactions/credit fetching and inter-tab plumbing.
Billing components & plans
app/src/components/settings/panels/billingHelpers.ts, .../SubscriptionPlans.tsx, .../PayAsYouGoCard.tsx, .../AutoRechargeSection.tsx
Update PLANS metadata (prices/features), redesign SubscriptionPlans layout, remove coupon flow from PayAsYouGoCard (add custom top-up), and remove AR dismiss prop.
Referral & coupon UI
app/src/components/rewards/ReferralRewardsSection.tsx, app/src/components/rewards/RewardsCouponSection.tsx
Restructure referral and coupon sections, change labels/placeholders to “Coupon code” / “Redeem Code”, relocate apply form, add copy/share UI.
Skills icons & categories
app/src/components/skills/skillIcons.tsx, app/src/components/skills/skillCategories.ts, app/src/components/skills/SkillCategoryFilter.tsx, app/src/pages/Skills.tsx
Introduce SkillCategory type/order, SkillIconBadge and category icon helpers, centralize built-in/channel icons, and replace category filter UI with PillTabBar.
Composio slug canonicalization & icons
app/src/components/composio/toolkitMeta.tsx, app/src/lib/composio/toolkitSlug.ts, app/src/lib/composio/hooks.ts
Canonicalize toolkit slugs via canonicalizeComposioToolkitSlug(), replace inline brand SVGs with SkillIconBadge, and use canonicalization in integration maps/lookups.
Usage state & Conversations banner
app/src/hooks/useUsageState.ts, app/src/hooks/useUsageState.test.ts, app/src/pages/Conversations.tsx
Add shouldShowBudgetCompletedMessage to usage state and tests; change Conversations banner visibility/message/CTA logic to use the new flag plus isRateLimited.
Credits API normalization & tests
app/src/services/api/creditsApi.ts, app/src/services/api/__tests__/billingApi.test.ts
Introduce normalizeUsd (accept numeric strings), make normalizeCreditBalance tolerant to multiple payload shapes and snake_case keys, and add tests for varied backend responses.
Channel auth serde & budget detection
src/openhuman/channels/controllers/definitions.rs, src/openhuman/channels/providers/web.rs
Use per-variant serde renames for ChannelAuthMode; add regex-based "budget exceeded" detection that converts some backend/agent errors into user-facing "top up" messages.
Settings navigation & small components
app/src/components/settings/SettingsHome.tsx, .../useSettingsNavigation.ts, .../components/PageBackButton.tsx, app/src/pages/Settings.tsx
Rename Account menu entry, add separate Billing & Usage entry, adjust breadcrumbs/navigation and wrapping (add footer wrapper), and add PageBackButton component.
Tests & minor copy updates
many tests under app/src/pages/__tests__, app/src/components/*/__tests__, app/src/hooks/*
Update tests for tabbed flows, new labels/placeholders, connect-modal copy, usage/billing expectations, and credits normalization tests.
Package / tooling
app/package.json, .github/workflows/release.yml
Add react-icons dependency; set VITE_OPENHUMAN_APP_ENV in typings and CI workflow; adjust Docker/release gating for staging builds.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant Page as Frontend Page
    participant Pill as PillTabBar<br/>(component)
    participant Tab as Tab Component
    participant API as Backend/API

    User->>Page: open page
    Page->>Pill: render PillTabBar(items)
    Pill->>User: show tab pills
    User->>Pill: click pill
    Pill->>Page: onChange(selected)
    Page->>Tab: mount selected Tab component
    Tab->>API: fetch data (via BACKEND_URL)
    API-->>Tab: return data
    Tab->>User: render tab content
Loading
sequenceDiagram
    participant Frontend as Frontend App
    participant Config as config.ts
    participant Env as Environment
    participant Backend as Backend API

    Frontend->>Config: read VITE_OPENHUMAN_APP_ENV & VITE_BACKEND_URL
    Config->>Env: retrieve vars
    Env-->>Config: return values
    Config->>Config: compute APP_ENV (trim/lowercase)
    Config->>Config: choose DEFAULT_BACKEND_URL (staging vs production)
    Config-->>Frontend: provide BACKEND_URL
    Frontend->>Backend: perform requests to BACKEND_URL
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through code, nose twitching at a flag,

staging whispers changed the root and backend tag.
Pills of tabs now bloom — Memory, Dreams, and Pay,
icons wore badges, tests danced in a tidy way.
A rabbit cheered: "All set — the UI finds its path!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Refine billing, settings, rewards, and usage UI' accurately describes the main changes across the PR, focusing on UI improvements to multiple interconnected features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (20)
src/openhuman/channels/controllers/definitions.rs (1)

434-452: Consider adding deserialize assertions to complete the wire-contract test.

This currently validates serialization only. Adding serde_json::from_value checks would guard inbound compatibility too.

Proposed test addition
     fn auth_mode_serializes_to_expected_wire_values() {
         assert_eq!(
             serde_json::to_value(ChannelAuthMode::ApiKey).expect("serialize"),
             serde_json::Value::String("api_key".to_string())
         );
@@
         assert_eq!(
             serde_json::to_value(ChannelAuthMode::ManagedDm).expect("serialize"),
             serde_json::Value::String("managed_dm".to_string())
         );
     }
+
+    #[test]
+    fn auth_mode_deserializes_from_expected_wire_values() {
+        assert_eq!(
+            serde_json::from_value::<ChannelAuthMode>(serde_json::Value::String("api_key".to_string()))
+                .expect("deserialize"),
+            ChannelAuthMode::ApiKey
+        );
+        assert_eq!(
+            serde_json::from_value::<ChannelAuthMode>(serde_json::Value::String("bot_token".to_string()))
+                .expect("deserialize"),
+            ChannelAuthMode::BotToken
+        );
+        assert_eq!(
+            serde_json::from_value::<ChannelAuthMode>(serde_json::Value::String("oauth".to_string()))
+                .expect("deserialize"),
+            ChannelAuthMode::OAuth
+        );
+        assert_eq!(
+            serde_json::from_value::<ChannelAuthMode>(serde_json::Value::String("managed_dm".to_string()))
+                .expect("deserialize"),
+            ChannelAuthMode::ManagedDm
+        );
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/channels/controllers/definitions.rs` around lines 434 - 452,
The test auth_mode_serializes_to_expected_wire_values only checks
serde_json::to_value; add reciprocal serde_json::from_value assertions to ensure
deserialization maps the wire strings back to the enum variants. For each
checked value ("api_key", "bot_token", "oauth", "managed_dm") call
serde_json::from_value::<ChannelAuthMode>(serde_json::Value::String(...)) and
assert_eq! the result to ChannelAuthMode::ApiKey, ChannelAuthMode::BotToken,
ChannelAuthMode::OAuth, and ChannelAuthMode::ManagedDm respectively so both
directions are covered.
src/openhuman/channels/providers/web.rs (1)

91-97: Harden the budget-error matcher to reduce false negatives.

The current matcher is strict and can miss common variants (for example, top-up or budget has been exceeded). Consider broadening the phrase set so budget exhaustion is consistently mapped to the friendly response.

Suggested refactor
 fn is_inference_budget_exceeded_error(message: &str) -> bool {
     let normalized = message.trim().to_ascii_lowercase();
-    normalized.contains("budget exceeded")
-        && (normalized.contains("add credits to continue")
-            || normalized.contains("add credits")
-            || normalized.contains("top up"))
+    let budget_phrase = normalized.contains("budget exceeded")
+        || normalized.contains("budget has been exceeded")
+        || normalized.contains("insufficient credits");
+    let action_phrase = normalized.contains("add credits")
+        || normalized.contains("credits to continue")
+        || normalized.contains("top up")
+        || normalized.contains("top-up");
+    budget_phrase && action_phrase
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/channels/providers/web.rs` around lines 91 - 97, The current
is_inference_budget_exceeded_error matcher is too strict and misses variants
like "top-up", "budget has been exceeded", or "out of credits"; replace the
simple substring checks with a broader, case-insensitive pattern (or multiple
patterns) that normalizes whitespace/hyphens and matches common variants such as
r"budget.*exceed", r"top[- ]?up", r"add.*credits", r"out of credits", and r"no
remaining credits"; update is_inference_budget_exceeded_error to trim and
to_ascii_lowercase the message, collapse repeated whitespace/hyphens, and test
against these regexes (prefer compiling them once as static/lazy to avoid
reallocation) so budget exhaustion messages are reliably detected.
src/openhuman/encryption/core.rs (1)

107-108: Implementation is correct; consider doc wording sync.

The path resolution change is solid. Optional follow-up: update the nearby docs to mention env-aware root names (e.g., staging suffix) instead of only ~/.openhuman.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/encryption/core.rs` around lines 107 - 108, Update the docs and
any nearby comments to reflect the env-aware root directory behavior introduced
by the path resolution change: replace or augment references to "~/.openhuman"
with a note that default_root_openhuman_dir() (in
src/openhuman/encryption/core.rs calling
crate::openhuman::config::default_root_openhuman_dir()) may return
environment-specific names (e.g., staging suffixes) and describe how to override
via env if applicable; ensure any README, comments, or docstrings near
default_root_openhuman_dir and its callers mention the env-aware naming
convention.
src/openhuman/agent/harness/definition_loader.rs (1)

126-128: Log root-dir resolution failures before returning None.

Line 126 currently drops resolver errors silently via .ok(). Adding a debug log here will make startup path issues traceable without changing behavior.

♻️ Suggested change
-    crate::openhuman::config::default_root_openhuman_dir()
-        .ok()
-        .map(|dir| dir.join("agents"))
+    match crate::openhuman::config::default_root_openhuman_dir() {
+        Ok(dir) => Some(dir.join("agents")),
+        Err(err) => {
+            tracing::debug!(
+                error = %err,
+                "[agent_defs] unable to resolve default root directory for user agents"
+            );
+            None
+        }
+    }

As per coding guidelines: "Add substantial, development-oriented logs while implementing features or fixes so issues are easy to trace end-to-end."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/openhuman/agent/harness/definition_loader.rs` around lines 126 - 128, The
call to crate::openhuman::config::default_root_openhuman_dir() currently
silences errors via .ok() — change this so any Err is logged before returning
None: call default_root_openhuman_dir(), match or map_err to capture the error,
emit a debug (or tracing::debug) log containing the error and context (e.g.,
"resolving root openhuman dir failed"), then proceed to return None or map to
dir.join("agents") on Ok; reference the default_root_openhuman_dir() invocation
and the subsequent .join("agents") mapping when making the change.
.env.example (1)

10-16: Optional: reorder keys to satisfy dotenv-linter key-order checks.

Current ordering triggers UnorderedKey warnings for BACKEND_URL and VITE_BACKEND_URL.

🔧 Suggested reorder
-OPENHUMAN_APP_ENV=staging
 BACKEND_URL=https://staging-api.tinyhumans.ai
+OPENHUMAN_APP_ENV=staging
- VITE_OPENHUMAN_APP_ENV=staging
 VITE_BACKEND_URL=https://staging-api.tinyhumans.ai
+VITE_OPENHUMAN_APP_ENV=staging
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.env.example around lines 10 - 16, The .env.example triggers dotenv-linter
UnorderedKey warnings for BACKEND_URL and VITE_BACKEND_URL; fix this by
reordering the keys so they follow the linter's expected ordering
(alphabetical/grouped by prefix). Specifically, move BACKEND_URL to sit
alphabetically with other non-VITE keys (after OPENHUMAN_APP_ENV) and place
VITE_BACKEND_URL so it is ordered correctly among VITE_* entries (after
VITE_OPENHUMAN_APP_ENV) to eliminate the UnorderedKey warnings for BACKEND_URL
and VITE_BACKEND_URL.
app/src/components/intelligence/IntelligenceSubconsciousTab.tsx (1)

57-67: Add [subconscious-ui] debug checkpoints around these new side effects.

This tab now owns task creation, manual runs, escalation decisions, toggles, removals, and navigation branches, but none of those transitions emit namespaced debug logs. That will make failures in these flows much harder to trace from app logs.

🪵 Example logging points
   const handleAddTask = async (e: FormEvent<HTMLFormElement>) => {
     e.preventDefault();
     const title = newTaskTitle.trim();
     if (!title) return;
     try {
+      console.debug('[subconscious-ui] add task start', { titleLen: title.length });
       await addSubconsciousTask(title);
       setNewTaskTitle('');
+      console.debug('[subconscious-ui] add task success');
     } catch {
+      console.debug('[subconscious-ui] add task failed');
       // handled by hook
     }
   };
console.debug('[subconscious-ui] trigger tick click');
console.debug('[subconscious-ui] escalation action', { escalationId: esc.id, action: 'approve' });
console.debug('[subconscious-ui] task toggle', { taskId: task.id, enabled: !task.enabled });

As per coding guidelines, "Add substantial, development-oriented logs while implementing features or fixes so issues are easy to trace end-to-end; log critical checkpoints including entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, and error handling paths."

Also applies to: 125-287

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/intelligence/IntelligenceSubconsciousTab.tsx` around lines
57 - 67, Add namespaced debug checkpoints "[subconscious-ui]" around all
side-effect handlers in this component: e.g., inside handleAddTask log entry
with the title before calling addSubconsciousTask, log success after
setNewTaskTitle('') and log failure in the catch; do the same pattern for other
side-effect functions in this file (e.g., handleRunTick / any "run tick"
handler, escalation handlers referenced by escalationId or esc.id, task toggle
handler like handleToggleTask that calls task toggle APIs, handleRemoveTask, and
any navigation branch functions) — each should emit
console.debug('[subconscious-ui] <action>', { relevantIds/state/branch }) at
entry, before/after external calls (addSubconsciousTask, manual run, escalation
approve/deny), and on error to aid tracing.
app/src/lib/composio/hooks.ts (1)

3-3: Decouple composio hook logic from UI component modules.

app/src/lib/composio/hooks.ts now depends on app/src/components/composio/toolkitMeta.tsx. Please move slug canonicalization into a non-UI shared module under app/src/lib/composio/ and import it from both places.

♻️ Minimal direction for the import boundary
-import { canonicalizeComposioToolkitSlug } from '../../components/composio/toolkitMeta';
+import { canonicalizeComposioToolkitSlug } from './toolkitSlug';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/lib/composio/hooks.ts` at line 3, The composio hook is importing
canonicalizeComposioToolkitSlug from a UI component module; extract the slug
canonicalization logic into a new non-UI shared module (e.g., create a plain
JS/TS file exporting canonicalizeComposioToolkitSlug under the composio lib),
move the implementation from toolkitMeta (remove it from the UI component), and
update both hooks.ts (which currently imports canonicalizeComposioToolkitSlug)
and the toolkitMeta component to import the function from the new shared module
so the hook no longer depends on UI code.
app/src/pages/__tests__/Rewards.test.tsx (1)

73-73: Avoid asserting duplicate loading-text count.

toHaveLength(2) couples this test to rendering structure instead of behavior. A single loading-state presence assertion is more stable.

✅ More behavior-focused assertion
-    expect(screen.getAllByText('Loading rewards…')).toHaveLength(2);
+    expect(screen.getByText('Loading rewards…')).toBeInTheDocument();

As per coding guidelines, "Prefer testing behavior over implementation details in Vitest unit tests".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/pages/__tests__/Rewards.test.tsx` at line 73, The test currently
asserts an exact duplicate loading-text count using screen.getAllByText('Loading
rewards…').toHaveLength(2'), which couples the test to implementation details;
replace that assertion with a behavior-focused presence check such as
expect(screen.getByText('Loading rewards…')).toBeInTheDocument() (or
expect(screen.queryByText('Loading rewards…')).not.toBeNull()) so the test only
verifies the loading state exists rather than how many times it’s rendered.
app/src/components/settings/panels/billing/BillingHistoryTab.tsx (2)

53-55: Five decimal places for USD display seems excessive.

toFixed(5) shows amounts like $0.00123 which is unusual for user-facing currency. Consider toFixed(2) for standard USD display, or if sub-cent precision is intentional (e.g., for micro-transactions), add a comment explaining why.

💰 Proposed fix for standard currency display
                   <div className={`font-semibold ${isEarn ? 'text-sage-600' : 'text-stone-950'}`}>
-                    {isEarn ? '+' : '-'}${Math.abs(transaction.amountUsd).toFixed(5)}
+                    {isEarn ? '+' : '-'}${Math.abs(transaction.amountUsd).toFixed(2)}
                   </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/BillingHistoryTab.tsx` around
lines 53 - 55, The USD amount in BillingHistoryTab is formatted with toFixed(5),
which is excessive for user-facing currency; update the display logic that
renders {isEarn ? '+' : '-'}${Math.abs(transaction.amountUsd).toFixed(5)} to use
toFixed(2) for standard USD formatting (or, if sub-cent precision is
intentionally required for micro-transactions, retain the higher precision but
add a clear comment near the transaction.amountUsd formatting explaining the
rationale and expected units/precision so future readers understand why 5
decimals are used).

44-50: Hardcoded 'en-US' locale for date formatting.

This is acceptable for now, but consider using undefined (user's default locale) or a shared locale constant if internationalization is planned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/BillingHistoryTab.tsx` around
lines 44 - 50, The date formatting in BillingHistoryTab.tsx hardcodes the
'en-US' locale for new Date(transaction.createdAt).toLocaleDateString; update
this to use the user's default locale (pass undefined instead of 'en-US') or a
shared locale constant (e.g., LOCALE or getUserLocale()) so the display respects
i18n; adjust the expression around transaction.createdAt in the
BillingHistoryTab component accordingly.
app/src/services/api/creditsApi.ts (2)

153-160: asNumber and normalizeUsd are identical—consolidate into one helper.

Both functions perform the same logic: check for finite number, parse string if non-empty, return fallback. Consider removing asNumber and using normalizeUsd throughout, or vice versa.

♻️ Proposed consolidation
-function asNumber(value: unknown): number {
-  if (typeof value === 'number' && Number.isFinite(value)) return value;
-  if (typeof value === 'string' && value.trim() !== '') {
-    const parsed = Number(value);
-    if (Number.isFinite(parsed)) return parsed;
-  }
-  return 0;
-}
+// Use normalizeUsd(value, 0) instead of asNumber(value)

Also applies to: 199-206

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/services/api/creditsApi.ts` around lines 153 - 160, The two helpers
asNumber and normalizeUsd contain identical logic; consolidate them into one
function (pick a single canonical name, e.g., normalizeUsd) and remove the
duplicate (delete asNumber or normalizeUsd accordingly), then replace all usages
of the removed name with the chosen helper (update callers that reference
asNumber and callers that reference normalizeUsd, including the code around the
current function and the other instance at lines referenced in the review).
Ensure the chosen helper is exported/visible where needed and keep the exact
behavior (finite number check, non-empty string parse, fallback 0).

208-231: Consider adding debug logging when normalization falls back to defaults.

The lenient parsing silently returns 0 when expected fields are missing. Per coding guidelines, adding debug logs for fallback cases would help trace data issues when the backend response shape changes unexpectedly.

🔍 Example debug logging
 function normalizeCreditBalance(payload: unknown): CreditBalance {
   const raw = (payload && typeof payload === 'object' ? payload : {}) as Record<string, unknown>;
   const nested = asRecord(raw.data) ?? asRecord(raw.balance) ?? null;
   const source = nested ?? raw;

+  if (import.meta.env.DEV) {
+    const hasPromoField = 'promotionBalanceUsd' in source || 'promotion_balance_usd' in source;
+    const hasTeamField = 'teamTopupUsd' in source || 'team_topup_usd' in source;
+    if (!hasPromoField || !hasTeamField) {
+      console.debug('[creditsApi] normalizeCreditBalance fallback', { hasPromoField, hasTeamField, keys: Object.keys(source) });
+    }
+  }
+
   return {
     promotionBalanceUsd: normalizeUsd(

As per coding guidelines: "Add substantial, development-oriented logs while implementing features or fixes so issues are easy to trace end-to-end."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/services/api/creditsApi.ts` around lines 208 - 231, The
normalizeCreditBalance function silently falls back to zeros when expected
fields are missing; update normalizeCreditBalance to detect when none of the
known balance keys are present on source (for promotionBalanceUsd and
teamTopupUsd) and emit a development debug log with the raw source object and
which keys were missing before returning normalized values, using the project's
debug/logger utility (e.g., logger.debug) and keep calls around normalizeUsd so
you can trace when fallback-to-default occurs; reference normalizeCreditBalance,
normalizeUsd and asRecord to locate where to add the checks/logging.
app/src/pages/Settings.tsx (2)

276-276: Consider adding a comment explaining why BillingPanel is not wrapped.

Unlike other leaf panels, BillingPanel renders without WrappedSettingsPage. This is intentional (it uses its own wider max-w-2xl layout), but a brief comment would help future maintainers understand the exception.

+        {/* BillingPanel uses its own wider layout (max-w-2xl) with internal tabs */}
         <Route path="billing" element={<BillingPanel />} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/pages/Settings.tsx` at line 276, Add a short inline comment in
Settings.tsx next to the Route for BillingPanel explaining why it is not wrapped
by WrappedSettingsPage (e.g., "BillingPanel intentionally not wrapped because it
uses its own wider max-w-2xl layout"); update the Route line (<Route
path="billing" element={<BillingPanel />} />) to include that comment so future
maintainers know this is an intentional exception rather than an oversight.

217-225: wrapSettingsPage is recreated on every render—consider memoizing or extracting.

Defining this helper inside the component body means a new function instance is created on each render. While React Router likely handles this gracefully, extracting it outside the component or wrapping with useCallback would be slightly more idiomatic.

♻️ Option: Extract outside component
+const wrapSettingsPage = (element: React.ReactNode) => (
+  <WrappedSettingsPage>
+    {element}
+    <div className="border-t border-stone-100 px-4 py-3 text-center text-[11px] text-stone-400">
+      Beta build - v{APP_VERSION}
+    </div>
+  </WrappedSettingsPage>
+);
+
 const Settings = () => {
-  const wrapSettingsPage = (element: React.ReactNode) => (
-    <WrappedSettingsPage>
-      {element}
-      <div className="border-t border-stone-100 px-4 py-3 text-center text-[11px] text-stone-400">
-        Beta build - v{APP_VERSION}
-      </div>
-    </WrappedSettingsPage>
-  );
-
   return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/pages/Settings.tsx` around lines 217 - 225, The helper
wrapSettingsPage defined inside the Settings component is recreated on every
render; move it outside the Settings function or memoize it with
React.useCallback to avoid allocating a new function each render. Locate the
wrapSettingsPage arrow function (which returns <WrappedSettingsPage> with
APP_VERSION) and either: 1) extract it as a top-level function that accepts
element: React.ReactNode, or 2) wrap its definition with useCallback inside
Settings (e.g., const wrapSettingsPage = useCallback((element) => ...,
[APP_VERSION])) so the function identity is stable. Ensure the JSX still renders
the Beta build v{APP_VERSION} and update any references to wrapSettingsPage
accordingly.
app/src/components/composio/toolkitMeta.tsx (1)

44-48: Inconsistent alias direction for googlesheets.

Other aliases map underscore variants to non-underscore canonical forms (google_calendargooglecalendar), but googlesheets maps to google_sheets (opposite direction). This works because CATALOG has both entries, but the inconsistency may cause confusion.

Consider aligning the direction:

♻️ Option A: Make google_sheets the canonical form consistently
 const TOOLKIT_ALIASES: Record<string, string> = {
   google_calendar: 'googlecalendar',
   google_drive: 'googledrive',
-  googlesheets: 'google_sheets',
+  // google_sheets is canonical; no alias needed if backend always sends google_sheets
 };
♻️ Option B: Align with other aliases (non-underscore canonical)
 const TOOLKIT_ALIASES: Record<string, string> = {
   google_calendar: 'googlecalendar',
   google_drive: 'googledrive',
-  googlesheets: 'google_sheets',
+  google_sheets: 'googlesheets',
 };

Then update KNOWN_COMPOSIO_TOOLKITS to use 'googlesheets' instead of 'google_sheets'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/composio/toolkitMeta.tsx` around lines 44 - 48,
TOOLKIT_ALIASES is inconsistent: change the googlesheets entry so the canonical
form matches the other aliases (map "google_sheets" -> "googlesheets" or vice
versa). Recommended: update TOOLKIT_ALIASES to map "google_sheets" to
"googlesheets" (so all mappings go from underscored to non-underscored canonical
names) and then update KNOWN_COMPOSIO_TOOLKITS to use "googlesheets" instead of
"google_sheets" wherever it appears.
app/src/components/PillTabBar.tsx (1)

27-45: Consider adding ARIA tab semantics for improved accessibility.

The component uses aria-pressed which is appropriate for toggle buttons, but since this functions as a tablist, using role="tablist" on the container and role="tab" on buttons would provide clearer semantics for screen readers.

♿ Proposed accessibility enhancement
   return (
-    <div className={containerClassName}>
+    <div className={containerClassName} role="tablist">
       {items.map(item => {
         const active = selected === item.value;

         return (
           <button
             key={item.value}
             type="button"
-            aria-pressed={active}
+            role="tab"
+            aria-selected={active}
             onClick={() => onChange(item.value)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/PillTabBar.tsx` around lines 27 - 45, This component is
presenting a tab list but uses aria-pressed; update PillTabBar to expose proper
ARIA tab semantics: add role="tablist" to the container (containerClassName),
set each button to role="tab" (in the items.map render), replace aria-pressed
with aria-selected={active} and manage keyboard focus via tabIndex={active ? 0 :
-1}; also ensure each tab has a stable id (e.g., derived from item.value) and,
if panels exist, include aria-controls linking to the panel id so screen readers
can associate tabs with their panels; keep onChange(selected) behavior intact
and leave renderItem/selected/activeClassName/inactiveClassName usage as-is.
app/src/components/settings/panels/billing/PayAsYouGoCard.tsx (2)

5-11: Remove unused onBalanceRefresh prop from interface.

The onBalanceRefresh prop is defined in PayAsYouGoCardProps but never destructured or used in the component (line 18). The parent BillingPaymentsTab still passes this prop, creating a misleading API contract.

♻️ Proposed fix
 interface PayAsYouGoCardProps {
   creditBalance: CreditBalance | null;
   isLoadingCredits: boolean;
   isToppingUp: boolean;
   onTopUp: (amountUsd: number) => void;
-  onBalanceRefresh: () => void;
 }

Also update the parent in BillingPaymentsTab.tsx to stop passing this prop.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/PayAsYouGoCard.tsx` around lines 5
- 11, Remove the unused onBalanceRefresh prop from the PayAsYouGoCardProps
interface and from the PayAsYouGoCard component’s props list: locate the
interface PayAsYouGoCardProps and delete the onBalanceRefresh declaration, then
update the PayAsYouGoCard component signature so it no longer expects or
destructures onBalanceRefresh; finally, update the parent BillingPaymentsTab to
stop passing onBalanceRefresh into PayAsYouGoCard so the prop contract is
consistent.

29-39: Derive validity inline instead of using useEffect to sync state.

Using useEffect to set customTopUpAmountValid based on customTopUpAmount creates an extra render cycle. This can be computed inline or via useMemo. Based on learnings, this codebase discourages synchronous setState calls inside useEffect bodies.

♻️ Proposed fix
-  const [customTopUpAmount, setCustomTopUpAmount] = useState('');
-  const [customTopUpAmountValid, setCustomTopUpAmountValid] = useState(false);
+  const [customTopUpAmount, setCustomTopUpAmount] = useState('');
+  const customTopUpAmountValid = Number(customTopUpAmount) > 0;

   const handleCustomTopUp = () => {
     if (!customTopUpAmountValid || isToppingUp) return;
     onTopUp(Number(customTopUpAmount));
   };
-
-  useEffect(() => {
-    setCustomTopUpAmountValid(Number(customTopUpAmount) > 0);
-  }, [customTopUpAmount]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/PayAsYouGoCard.tsx` around lines
29 - 39, customTopUpAmountValid is derived from customTopUpAmount via a
useEffect causing an extra render; remove the customTopUpAmountValid state and
the useEffect, compute validity inline (or with useMemo) from customTopUpAmount
(e.g., const customTopUpAmountValid = Number(customTopUpAmount) > 0) and update
handleCustomTopUp to reference that derived value; delete
setCustomTopUpAmountValid and the useEffect block to avoid synchronous state
updates.
app/src/components/settings/panels/billing/SubscriptionPlans.tsx (1)

202-202: Consider removing commented-out code.

The tagline rendering is commented out. If this is intentionally disabled, consider removing the dead code entirely to reduce noise.

♻️ Proposed fix
-                  {/* {plan.tagline && <p className="mt-1 text-sm text-stone-500">{plan.tagline}</p>} */}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/settings/panels/billing/SubscriptionPlans.tsx` at line
202, The commented-out JSX line rendering plan.tagline inside the
SubscriptionPlans component is dead code and should be removed to reduce noise;
locate the commented snippet referencing plan.tagline (the line with {/*
{plan.tagline && <p className="mt-1 text-sm text-stone-500">{plan.tagline}</p>}
*/}) in SubscriptionPlans.tsx and delete it, or if the tagline may be needed
later, replace the comment with a clear TODO explaining why it’s disabled and
reference the plan.tagline conditional rendering for future restoration.
app/src/components/rewards/RewardsCommunityTab.tsx (1)

68-80: Remove unused onSelectRedeem and plan props.

The props onSelectRedeem and plan are defined in RewardsCommunityTabProps but never destructured or used in the component body. This creates a misleading API contract.

♻️ Proposed fix
 interface RewardsCommunityTabProps {
   error: string | null;
   isLoading: boolean;
-  onSelectRedeem: () => void;
-  plan: string;
   snapshot: RewardsSnapshot | null;
 }

 export default function RewardsCommunityTab({
   error,
   isLoading,
   snapshot,
 }: RewardsCommunityTabProps) {

Also update the parent Rewards.tsx to stop passing these unused props.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/components/rewards/RewardsCommunityTab.tsx` around lines 68 - 80,
Remove the unused props from the component API: delete onSelectRedeem and plan
from the RewardsCommunityTabProps interface and stop expecting/destructuring
them in the RewardsCommunityTab function; then update the parent component
(Rewards.tsx) to stop passing onSelectRedeem and plan into <RewardsCommunityTab
/> so the prop contract and usage stay consistent (refer to
RewardsCommunityTabProps, RewardsCommunityTab, onSelectRedeem, plan, and the
parent Rewards.tsx component to locate the changes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/components/intelligence/IntelligenceDreamsTab.tsx`:
- Around line 21-24: The paragraph in the IntelligenceDreamsTab component
contains typos and grammar issues; update the text node (inside the <p> with
className "text-stone-400 text-sm mb-1") to read something like: "Twice every
day, OpenHuman will generate a dream (or a summary) based on everything that has
happened in your life today. These dreams are then indexed and can be used to
influence OpenHuman's behavior." Replace the existing string in
IntelligenceDreamsTab accordingly.

In `@app/src/components/intelligence/IntelligenceMemoryTab.tsx`:
- Around line 37-59: In IntelligenceMemoryTab.tsx the search input
(searchFilter/setSearchFilter) and source select (sourceFilter/setSourceFilter)
lack accessible names for screen readers; add programmatic labels by either
wrapping each control with a <label htmlFor="..."> and setting matching id
attributes (e.g., id="actionable-search" and id="actionable-source") or adding
explicit aria-label or aria-labelledby attributes that describe their purpose
(e.g., "Search actionable items" and "Filter by source"), ensuring the select
options remain unchanged and the onChange handlers (setSearchFilter,
setSourceFilter) continue to work.

In `@app/src/components/intelligence/IntelligenceSubconsciousTab.tsx`:
- Around line 179-184: The onClick currently calls dismissEscalation(esc.id)
before navigate('/skills'), which removes the escalation immediately and loses
the reminder if the user doesn't actually fix it; remove the immediate
dismissEscalation call from the onClick handler and instead navigate to the
Skills page carrying the escalation id (e.g., via route state or a query param)
so the Skills component (or the fix action there) can call
dismissEscalation(esc.id) only after the user resolves the issue; update related
handlers in the Skills page to read the passed esc id and perform the dismissal
when the fix completes.
- Around line 236-267: The toggle and remove buttons lack accessible names and
state; update the toggle button (where toggleSubconsciousTask is called) to
include an accessible name and state (e.g., set aria-pressed based on
task.enabled and a dynamic aria-label like "Enable {task.title}" / "Disable
{task.title}" so screen readers know the action and current state), and update
the remove button (where removeSubconsciousTask is called) to include a
descriptive aria-label such as "Remove {task.title}" (or add visually-hidden
text tied to the button) so the icon-only control is announced; keep task.title
and task.enabled to build the dynamic labels.

In `@app/src/components/rewards/ReferralRewardsSection.tsx`:
- Around line 263-270: The input currently uppercases every keystroke
(setApplyCode(e.target.value.toUpperCase())), which mangles pasted URLs; instead
stop mutating input onChange (use setApplyCode(e.target.value)) so applyCode
preserves the raw value, and move normalization into handleApply: trim
applyCode, detect URLs (e.g. /^https?:\/\//i.test(trimmed>) and if it’s a URL
keep it verbatim, otherwise uppercase the code, then call
referralApi.claimReferral or claimReferral(value) with that normalized value;
update references to setApplyCode, applyCode, and handleApply accordingly.

In `@app/src/components/settings/panels/billing/BillingPaymentsTab.tsx`:
- Around line 71-77: The PayAsYouGoCard is passed onBalanceRefresh but the
component doesn't destructure or call it; update the PayAsYouGoCard component to
accept onBalanceRefresh in its props (add it to the props interface /
destructuring) and invoke it from the appropriate UI action (for example, wire
it to the refresh button's onClick or the balance refresh handler inside
PayAsYouGoCard), or alternatively remove the onBalanceRefresh prop from the
parent (BillingPaymentsTab) if no refresh UI is needed; reference PayAsYouGoCard
and the onBalanceRefresh prop when making the change.

In `@app/src/components/skills/skillIcons.tsx`:
- Around line 37-45: The span that renders the labeled icon badge (the element
using aria-label and className generated by iconClasses and rendering <Icon
aria-hidden="true" />) should include role="img" so assistive technologies treat
it as an image; update the span to add role="img" (keeping aria-label and
preserving Icon's aria-hidden) to provide correct semantics for the labeled icon
badge.

In `@src/api/config.rs`:
- Line 23: Update the doc comment for the API base URL fallback to reflect the
new env-aware behavior: state that the function now falls back to
default_api_base_url_for_env(app_env_from_env().as_deref()) (which selects a
DEFAULT_API_BASE_URL based on the current APP_ENV) rather than unconditionally
using DEFAULT_API_BASE_URL; mention the involved symbols
default_api_base_url_for_env and app_env_from_env so readers know the fallback
is environment-aware.

In `@src/openhuman/channels/providers/web.rs`:
- Around line 355-361: The current log::warn call in the web channel is emitting
the raw err_message which can leak sensitive upstream/provider payloads; replace
the raw err_message with a sanitized classification or redacted summary instead.
Locate the log::warn invocation (the lines with client_id, thread_id, request_id
and err_message) and change it to log a fixed classification string or the
result of a sanitizer helper (e.g., sanitize_error(err_message) or
map_err_to_category(err_message)) that strips tokens/PII and only returns a
non-sensitive category or short summary; ensure no full JWTs, API keys, or PII
are included in the logged value.

In `@src/openhuman/config/ops.rs`:
- Around line 42-44: The fallback to PathBuf::from(".openhuman") is not
environment-aware; change the unwrap_or_else closure used after
crate::openhuman::config::default_root_openhuman_dir() so it reads an env var
(e.g., OPENHUMAN_ENV) and builds a suffix (empty for production or "-<env>" for
staging/other) and returns PathBuf::from(format!(".openhuman{}", suffix)).
Replace both occurrences (the two places currently using .unwrap_or_else(|_|
PathBuf::from(".openhuman")).join("workspace")) so the fallback becomes
environment-scoped before calling .join("workspace").

In `@src/openhuman/config/schema/load.rs`:
- Around line 1274-1282: The test
default_root_dir_name_uses_staging_suffix_for_staging_env currently checks
is_staging_app_env(Some("staging")) against a literal instead of exercising
default_root_dir_name(); update the test to call the function
default_root_dir_name() (and optionally also call it for a non-staging env) and
assert that it returns ".openhuman-staging" when
crate::api::config::is_staging_app_env(Some("staging")) is true, and
".openhuman" when the env is non-staging, so the mapping implemented by
default_root_dir_name() is validated directly.

In `@src/openhuman/config/schema/types.rs`:
- Around line 201-206: The fallback for openhuman_dir in Config::default()
always uses ".openhuman" and thus loses any staging suffix; update the
unwrap_or_else closure so it constructs the fallback path then preserves/appends
the staging suffix used elsewhere (e.g., call the existing
crate::openhuman::config helper such as staging_suffix() or is_staging() to
determine the suffix) instead of hardcoding ".openhuman". Concretely, inside the
closure that builds home.join(".openhuman"), conditionally append the staging
suffix obtained from crate::openhuman::config (or call a provided
staging_suffix() function) so openhuman_dir honors the same staging/prod naming
as default_root_openhuman_dir().

---

Nitpick comments:
In @.env.example:
- Around line 10-16: The .env.example triggers dotenv-linter UnorderedKey
warnings for BACKEND_URL and VITE_BACKEND_URL; fix this by reordering the keys
so they follow the linter's expected ordering (alphabetical/grouped by prefix).
Specifically, move BACKEND_URL to sit alphabetically with other non-VITE keys
(after OPENHUMAN_APP_ENV) and place VITE_BACKEND_URL so it is ordered correctly
among VITE_* entries (after VITE_OPENHUMAN_APP_ENV) to eliminate the
UnorderedKey warnings for BACKEND_URL and VITE_BACKEND_URL.

In `@app/src/components/composio/toolkitMeta.tsx`:
- Around line 44-48: TOOLKIT_ALIASES is inconsistent: change the googlesheets
entry so the canonical form matches the other aliases (map "google_sheets" ->
"googlesheets" or vice versa). Recommended: update TOOLKIT_ALIASES to map
"google_sheets" to "googlesheets" (so all mappings go from underscored to
non-underscored canonical names) and then update KNOWN_COMPOSIO_TOOLKITS to use
"googlesheets" instead of "google_sheets" wherever it appears.

In `@app/src/components/intelligence/IntelligenceSubconsciousTab.tsx`:
- Around line 57-67: Add namespaced debug checkpoints "[subconscious-ui]" around
all side-effect handlers in this component: e.g., inside handleAddTask log entry
with the title before calling addSubconsciousTask, log success after
setNewTaskTitle('') and log failure in the catch; do the same pattern for other
side-effect functions in this file (e.g., handleRunTick / any "run tick"
handler, escalation handlers referenced by escalationId or esc.id, task toggle
handler like handleToggleTask that calls task toggle APIs, handleRemoveTask, and
any navigation branch functions) — each should emit
console.debug('[subconscious-ui] <action>', { relevantIds/state/branch }) at
entry, before/after external calls (addSubconsciousTask, manual run, escalation
approve/deny), and on error to aid tracing.

In `@app/src/components/PillTabBar.tsx`:
- Around line 27-45: This component is presenting a tab list but uses
aria-pressed; update PillTabBar to expose proper ARIA tab semantics: add
role="tablist" to the container (containerClassName), set each button to
role="tab" (in the items.map render), replace aria-pressed with
aria-selected={active} and manage keyboard focus via tabIndex={active ? 0 : -1};
also ensure each tab has a stable id (e.g., derived from item.value) and, if
panels exist, include aria-controls linking to the panel id so screen readers
can associate tabs with their panels; keep onChange(selected) behavior intact
and leave renderItem/selected/activeClassName/inactiveClassName usage as-is.

In `@app/src/components/rewards/RewardsCommunityTab.tsx`:
- Around line 68-80: Remove the unused props from the component API: delete
onSelectRedeem and plan from the RewardsCommunityTabProps interface and stop
expecting/destructuring them in the RewardsCommunityTab function; then update
the parent component (Rewards.tsx) to stop passing onSelectRedeem and plan into
<RewardsCommunityTab /> so the prop contract and usage stay consistent (refer to
RewardsCommunityTabProps, RewardsCommunityTab, onSelectRedeem, plan, and the
parent Rewards.tsx component to locate the changes).

In `@app/src/components/settings/panels/billing/BillingHistoryTab.tsx`:
- Around line 53-55: The USD amount in BillingHistoryTab is formatted with
toFixed(5), which is excessive for user-facing currency; update the display
logic that renders {isEarn ? '+' :
'-'}${Math.abs(transaction.amountUsd).toFixed(5)} to use toFixed(2) for standard
USD formatting (or, if sub-cent precision is intentionally required for
micro-transactions, retain the higher precision but add a clear comment near the
transaction.amountUsd formatting explaining the rationale and expected
units/precision so future readers understand why 5 decimals are used).
- Around line 44-50: The date formatting in BillingHistoryTab.tsx hardcodes the
'en-US' locale for new Date(transaction.createdAt).toLocaleDateString; update
this to use the user's default locale (pass undefined instead of 'en-US') or a
shared locale constant (e.g., LOCALE or getUserLocale()) so the display respects
i18n; adjust the expression around transaction.createdAt in the
BillingHistoryTab component accordingly.

In `@app/src/components/settings/panels/billing/PayAsYouGoCard.tsx`:
- Around line 5-11: Remove the unused onBalanceRefresh prop from the
PayAsYouGoCardProps interface and from the PayAsYouGoCard component’s props
list: locate the interface PayAsYouGoCardProps and delete the onBalanceRefresh
declaration, then update the PayAsYouGoCard component signature so it no longer
expects or destructures onBalanceRefresh; finally, update the parent
BillingPaymentsTab to stop passing onBalanceRefresh into PayAsYouGoCard so the
prop contract is consistent.
- Around line 29-39: customTopUpAmountValid is derived from customTopUpAmount
via a useEffect causing an extra render; remove the customTopUpAmountValid state
and the useEffect, compute validity inline (or with useMemo) from
customTopUpAmount (e.g., const customTopUpAmountValid =
Number(customTopUpAmount) > 0) and update handleCustomTopUp to reference that
derived value; delete setCustomTopUpAmountValid and the useEffect block to avoid
synchronous state updates.

In `@app/src/components/settings/panels/billing/SubscriptionPlans.tsx`:
- Line 202: The commented-out JSX line rendering plan.tagline inside the
SubscriptionPlans component is dead code and should be removed to reduce noise;
locate the commented snippet referencing plan.tagline (the line with {/*
{plan.tagline && <p className="mt-1 text-sm text-stone-500">{plan.tagline}</p>}
*/}) in SubscriptionPlans.tsx and delete it, or if the tagline may be needed
later, replace the comment with a clear TODO explaining why it’s disabled and
reference the plan.tagline conditional rendering for future restoration.

In `@app/src/lib/composio/hooks.ts`:
- Line 3: The composio hook is importing canonicalizeComposioToolkitSlug from a
UI component module; extract the slug canonicalization logic into a new non-UI
shared module (e.g., create a plain JS/TS file exporting
canonicalizeComposioToolkitSlug under the composio lib), move the implementation
from toolkitMeta (remove it from the UI component), and update both hooks.ts
(which currently imports canonicalizeComposioToolkitSlug) and the toolkitMeta
component to import the function from the new shared module so the hook no
longer depends on UI code.

In `@app/src/pages/__tests__/Rewards.test.tsx`:
- Line 73: The test currently asserts an exact duplicate loading-text count
using screen.getAllByText('Loading rewards…').toHaveLength(2'), which couples
the test to implementation details; replace that assertion with a
behavior-focused presence check such as expect(screen.getByText('Loading
rewards…')).toBeInTheDocument() (or expect(screen.queryByText('Loading
rewards…')).not.toBeNull()) so the test only verifies the loading state exists
rather than how many times it’s rendered.

In `@app/src/pages/Settings.tsx`:
- Line 276: Add a short inline comment in Settings.tsx next to the Route for
BillingPanel explaining why it is not wrapped by WrappedSettingsPage (e.g.,
"BillingPanel intentionally not wrapped because it uses its own wider max-w-2xl
layout"); update the Route line (<Route path="billing" element={<BillingPanel
/>} />) to include that comment so future maintainers know this is an
intentional exception rather than an oversight.
- Around line 217-225: The helper wrapSettingsPage defined inside the Settings
component is recreated on every render; move it outside the Settings function or
memoize it with React.useCallback to avoid allocating a new function each
render. Locate the wrapSettingsPage arrow function (which returns
<WrappedSettingsPage> with APP_VERSION) and either: 1) extract it as a top-level
function that accepts element: React.ReactNode, or 2) wrap its definition with
useCallback inside Settings (e.g., const wrapSettingsPage =
useCallback((element) => ..., [APP_VERSION])) so the function identity is
stable. Ensure the JSX still renders the Beta build v{APP_VERSION} and update
any references to wrapSettingsPage accordingly.

In `@app/src/services/api/creditsApi.ts`:
- Around line 153-160: The two helpers asNumber and normalizeUsd contain
identical logic; consolidate them into one function (pick a single canonical
name, e.g., normalizeUsd) and remove the duplicate (delete asNumber or
normalizeUsd accordingly), then replace all usages of the removed name with the
chosen helper (update callers that reference asNumber and callers that reference
normalizeUsd, including the code around the current function and the other
instance at lines referenced in the review). Ensure the chosen helper is
exported/visible where needed and keep the exact behavior (finite number check,
non-empty string parse, fallback 0).
- Around line 208-231: The normalizeCreditBalance function silently falls back
to zeros when expected fields are missing; update normalizeCreditBalance to
detect when none of the known balance keys are present on source (for
promotionBalanceUsd and teamTopupUsd) and emit a development debug log with the
raw source object and which keys were missing before returning normalized
values, using the project's debug/logger utility (e.g., logger.debug) and keep
calls around normalizeUsd so you can trace when fallback-to-default occurs;
reference normalizeCreditBalance, normalizeUsd and asRecord to locate where to
add the checks/logging.

In `@src/openhuman/agent/harness/definition_loader.rs`:
- Around line 126-128: The call to
crate::openhuman::config::default_root_openhuman_dir() currently silences errors
via .ok() — change this so any Err is logged before returning None: call
default_root_openhuman_dir(), match or map_err to capture the error, emit a
debug (or tracing::debug) log containing the error and context (e.g., "resolving
root openhuman dir failed"), then proceed to return None or map to
dir.join("agents") on Ok; reference the default_root_openhuman_dir() invocation
and the subsequent .join("agents") mapping when making the change.

In `@src/openhuman/channels/controllers/definitions.rs`:
- Around line 434-452: The test auth_mode_serializes_to_expected_wire_values
only checks serde_json::to_value; add reciprocal serde_json::from_value
assertions to ensure deserialization maps the wire strings back to the enum
variants. For each checked value ("api_key", "bot_token", "oauth", "managed_dm")
call serde_json::from_value::<ChannelAuthMode>(serde_json::Value::String(...))
and assert_eq! the result to ChannelAuthMode::ApiKey, ChannelAuthMode::BotToken,
ChannelAuthMode::OAuth, and ChannelAuthMode::ManagedDm respectively so both
directions are covered.

In `@src/openhuman/channels/providers/web.rs`:
- Around line 91-97: The current is_inference_budget_exceeded_error matcher is
too strict and misses variants like "top-up", "budget has been exceeded", or
"out of credits"; replace the simple substring checks with a broader,
case-insensitive pattern (or multiple patterns) that normalizes
whitespace/hyphens and matches common variants such as r"budget.*exceed",
r"top[- ]?up", r"add.*credits", r"out of credits", and r"no remaining credits";
update is_inference_budget_exceeded_error to trim and to_ascii_lowercase the
message, collapse repeated whitespace/hyphens, and test against these regexes
(prefer compiling them once as static/lazy to avoid reallocation) so budget
exhaustion messages are reliably detected.

In `@src/openhuman/encryption/core.rs`:
- Around line 107-108: Update the docs and any nearby comments to reflect the
env-aware root directory behavior introduced by the path resolution change:
replace or augment references to "~/.openhuman" with a note that
default_root_openhuman_dir() (in src/openhuman/encryption/core.rs calling
crate::openhuman::config::default_root_openhuman_dir()) may return
environment-specific names (e.g., staging suffixes) and describe how to override
via env if applicable; ensure any README, comments, or docstrings near
default_root_openhuman_dir and its callers mention the env-aware naming
convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4aaa561f-7ada-4ad2-a7ff-ae253c1767ff

📥 Commits

Reviewing files that changed from the base of the PR and between f26a0c5 and 3af6894.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (52)
  • .env.example
  • app/.env.example
  • app/package.json
  • app/src/components/PillTabBar.tsx
  • app/src/components/composio/ComposioConnectModal.tsx
  • app/src/components/composio/toolkitMeta.tsx
  • app/src/components/intelligence/IntelligenceDreamsTab.tsx
  • app/src/components/intelligence/IntelligenceMemoryTab.tsx
  • app/src/components/intelligence/IntelligenceSubconsciousTab.tsx
  • app/src/components/rewards/ReferralRewardsSection.tsx
  • app/src/components/rewards/RewardsCommunityTab.tsx
  • app/src/components/rewards/RewardsCouponSection.tsx
  • app/src/components/rewards/RewardsRedeemTab.tsx
  • app/src/components/rewards/RewardsReferralsTab.tsx
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/components/PageBackButton.tsx
  • app/src/components/settings/hooks/useSettingsNavigation.ts
  • app/src/components/settings/panels/BillingPanel.tsx
  • app/src/components/settings/panels/billing/AutoRechargeSection.tsx
  • app/src/components/settings/panels/billing/BillingHistoryTab.tsx
  • app/src/components/settings/panels/billing/BillingPaymentsTab.tsx
  • app/src/components/settings/panels/billing/BillingPlansTab.tsx
  • app/src/components/settings/panels/billing/PayAsYouGoCard.tsx
  • app/src/components/settings/panels/billing/SubscriptionPlans.tsx
  • app/src/components/settings/panels/billingHelpers.ts
  • app/src/components/skills/SkillCategoryFilter.tsx
  • app/src/components/skills/skillCategories.ts
  • app/src/components/skills/skillIcons.tsx
  • app/src/hooks/useUsageState.test.ts
  • app/src/hooks/useUsageState.ts
  • app/src/lib/composio/hooks.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/Intelligence.tsx
  • app/src/pages/Rewards.tsx
  • app/src/pages/Settings.tsx
  • app/src/pages/Skills.tsx
  • app/src/pages/__tests__/Rewards.test.tsx
  • app/src/pages/__tests__/Skills.third-party-notion-debug-tools.test.tsx
  • app/src/services/api/__tests__/billingApi.test.ts
  • app/src/services/api/creditsApi.ts
  • app/src/utils/config.ts
  • app/src/vite-env.d.ts
  • src/api/config.rs
  • src/openhuman/agent/harness/definition_loader.rs
  • src/openhuman/channels/controllers/definitions.rs
  • src/openhuman/channels/providers/web.rs
  • src/openhuman/config/ops.rs
  • src/openhuman/config/schema/load.rs
  • src/openhuman/config/schema/types.rs
  • src/openhuman/encryption/core.rs
  • src/openhuman/memory/global.rs
  • src/openhuman/memory/store/client.rs

Comment thread app/src/components/intelligence/IntelligenceDreamsTab.tsx
Comment thread app/src/components/intelligence/IntelligenceMemoryTab.tsx
Comment thread app/src/components/intelligence/IntelligenceSubconsciousTab.tsx Outdated
Comment thread app/src/components/intelligence/IntelligenceSubconsciousTab.tsx
Comment thread app/src/components/rewards/ReferralRewardsSection.tsx
Comment thread src/api/config.rs
Comment thread src/openhuman/channels/providers/web.rs
Comment thread src/openhuman/config/ops.rs
Comment thread src/openhuman/config/schema/load.rs Outdated
Comment thread src/openhuman/config/schema/types.rs
…nd consistency

- Renamed placeholder text and button labels in the RewardsCouponSection test to reflect updated terminology.
- Adjusted test assertions to ensure they align with the new button and placeholder names.
- Updated pricing details in billingHelpers tests to reflect new monthly and annual rates for BASIC and PRO plans.
- Enhanced the ContextGatheringStep labels for better clarity regarding email and LinkedIn processing stages.
- Reformatted the coupon code input changes in the RewardsCouponSection test for improved readability and consistency.
- Ensured that the test cases maintain a uniform style for better maintainability.
… components

- Added ARIA roles and attributes to the PillTabBar for better accessibility.
- Refactored the canonicalization logic for toolkit slugs into a new file for improved organization.
- Updated the IntelligenceDreamsTab and IntelligenceMemoryTab components for better readability and accessibility.
- Enhanced error handling and logging in the IntelligenceSubconsciousTab for improved debugging.
- Streamlined the ReferralRewardsSection and RewardsCommunityTab components by normalizing referral code input handling.
- Removed unused props and improved layout consistency in BillingPanel and related components.
- Updated the Skills page to handle subconscious escalation dismissals more effectively.
- Added steps to configure the staging app environment in the release workflow.
- Set environment variables for staging, including OPENHUMAN_APP_ENV and VITE_OPENHUMAN_APP_ENV.
- Updated build and deployment steps to conditionally use staging settings based on the build target.
- Ensured proper handling of workspace paths for staging deployments.
- Introduced OPENHUMAN_APP_ENV variable to specify the app environment as 'staging'.
- Updated .env.example to include new environment configuration for clarity.
…ting

- Simplified the navigation logic in the IntelligenceSubconsciousTab for better readability.
- Improved text formatting in the IntelligenceDreamsTab for enhanced clarity and consistency.
- Refactored import statements in toolkitMeta.tsx for better organization.
@senamakel senamakel merged commit 5a8a7ed into tinyhumansai:main Apr 14, 2026
7 of 8 checks passed
@senamakel senamakel deleted the fix/monday-3 branch April 14, 2026 01:11
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.

1 participant