Skip to content

feat: introduce core infrastructure (media, cache, API)#10

Open
rferrari wants to merge 47 commits intoSkateHive:mainfrom
rferrari:pr/core-infra
Open

feat: introduce core infrastructure (media, cache, API)#10
rferrari wants to merge 47 commits intoSkateHive:mainfrom
rferrari:pr/core-infra

Conversation

@rferrari
Copy link
Copy Markdown
Collaborator

@rferrari rferrari commented Mar 18, 2026

🎯 Summary

Foundation PR introducing core infrastructure changes: modular media system, cache layer, API environment config, and mute/blacklist filtering.

📦 What's Included

Unified Media System

  • EmbedFactory + UniversalRenderer for modular multimedia rendering
  • Embed components: Image, Video, Instagram, Snapshot, Zora
  • MarkdownProcessor for content parsing

Lazy Media & Provider System

  • Lazy media rendering pipeline
  • Modular provider registry (IPFS, YouTube, Vimeo, Odysee, ThreeSpeak, Image)
  • BaseVideoEmbed component

Environment-based API URLs

  • Moved API URLs from hardcoded values to .env environment variables

Core Mute Filtering

  • Mute/follow UI on profile page
  • getMutedList, getBlacklistedList, getFollowingList, getFollowersList in api.ts
  • API-first with RPC fallback pattern (no resilient-fetch dependency)

Followers Cache Enhancement

  • Cache layer for followers data in auth-provider

Phase 0 (Cache) + Phase 1 (Wallet)

  • Cache infrastructure
  • Blockchain wallet hooks
  • Resilient fetch utility

🔧 Conflict Resolution Notes

Commit File(s) Resolution
6386cbc (Mute Filtering) profile.tsx, leaderboard.tsx Accepted incoming — additive mute UI feature
10a8392 (API muted list) resilient-fetch.ts Deleted (CASE A: deleted in current branch)
10a8392 (API muted list) api.ts Rewrote to inline fetch + RPC fallback (removed resilient-fetch dep)
10a8392 (API muted list) auth-provider.tsx Accepted incoming — API integration
9b89a11 (Followers cache) auth-provider.tsx Kept ours — preserves API wrapper pattern

🧪 Testing

  • App builds without errors
  • Feed loads correctly
  • Profile mute/follow buttons functional
  • Media embeds render (YouTube, images, IPFS)
  • Wallet data loads

Summary by CodeRabbit

  • New Features

    • Search screen, unified in-app conversation drawer, comment bottom sheet, Side Menu, Global Header, avatar tab bar, feed filter controls
    • Modular media embeds with image modal and video embed components; universal markdown renderer
    • Vote preset buttons and inline follow/mute actions across feed and profile
  • Improvements

    • Lazy media mounting, focus-aware video autoplay, scroll-lock, app settings, session persistence and prefetching
    • Muted-user filtering applied to feeds and leaderboard; branding updated to “Skatehive”
  • Bug Fixes

    • Resilient API/RPC fallbacks and improved relationship loading
  • Documentation

    • README and agent guides rewritten and consolidated

rferrari added 30 commits March 13, 2026 05:46
…rely moved to the Side Menu with a confirmation dialog
It now features a philosophy section inspired by PeakD (Ownership, Decentralization, Rewards) adapted specifically for the skating community, along with a "Built by the Homies" section introducing the core team
Key improvements:
Tab Unmounting: Heavy screens like videos and feed now unmount on blur. This releases all their memory (videos, large lists, image caches) whenever you switch to another tab.
Focus-Aware Video: Videos will now stop and release resources as soon as you switch tabs, even before unmounting.
Background Optimization: Visibility tracking now stops when the app is in the background, saving CPU and battery.
Cleaner Architecture: Removed redundant providers and fixed layout syntax errors.
…drawer.

By tracking the scroll position, the drawer will now close when you swipe down from anywhere—header or comment list—provided you are at the top of the conversation. This keeps the experience intuitive without interfering with your scrolling.

Try it out by swiping down from the comments themselves! 🛹✨
By setting flexGrow: 1 on the comment list container, the entire drawer surface is now touchable and responsive to the swipe gesture. I also updated the PanResponder to be even more aggressive about capturing swipes when you're at the top of the conversation.
Here's what changed:

Videos Tab: Header is now hidden for total immersion.
Header Refinement: Notifications moved to the left; Search (or Settings on Profile) on the right.
Personalized Tab Bar: Your profile avatar now lives in the bottom menu, replacing the generic person icon.
Key Improvements:

Multi-View Navigation: The side menu now features a main "Settings" view that slides seamlessly into a detailed "Accounts" view.
Account Card: Your Hive account is now highlighted with a rounded avatar and UID.
Accounts Detail:
Large center avatar for clear identification.
Click-to-Copy: Just tap your name or UID to copy your Hive username to the clipboard.
Session Tracking: View all active Hive sessions and upcoming social slots (Farcaster, Lens, etc.).
Integrated Editing: The "Edit" button opens the profile modification modal directly.
Safety: "Delete Account" has been renamed to "Remove from Device" as requested.
Clean Design: Grouped card-based layout for a premium, organized feel.
rferrari added 10 commits March 14, 2026 02:16
…equested! 🛹🔄

What's new:

Stance Toggle: You can now choose between Regular and Goofy in the Appearance settings.
Goofy Mode: Mirrors the UI controls! Buttons (Vote/Comment) move to the left, and the profile info moves to the right on both the Videos (Home) and Feed screens.
Here's a summary of what's now live:

Token-Based Tokenization: Efficiently identifies all specialized links (YouTube, Odysee, Zora, etc.).
Unified Rendering: A single UniversalRenderer that mixes markdown text with rich, native-feeling embeds.
Premium Embeds: Dedicated components for Video, Instagram, Zora, Snapshot, and Images.
Improved UX: Images now support full-screen viewing with a sleek modal.
Clean Architecture: Refactored PostCard.tsx to use this modular system, making it much easier to maintain.
🚀 Key Improvements:
Stop the Jump: WebViews now only mount when they enter the screen. This physically prevents them from stealing focus or playing audio while they are off-screen, solving the "autoscroll nightmare."
Modular Providers: Each media type (YouTube/Odysee/IPFS/etc.) is now an isolated provider. Adding new ones is now a "plug-and-play" operation.
Silent by Default: All videos are strictly silenced and require user interaction to play.
Performance: The Snaps feed is now much lighter because it only ever renders the few videos you are actually looking at.
… built:

New Files:

lib/resilient-fetch.ts — Generic resilientFetch<T>() helper. Tries the Skatehive API first, falls back to dHive RPC, normalizes both to the same interface.
API Enhancement:

v2/balance/[username]/route.ts — Now returns delegated_hp, received_hp, hbd_claimable by JOINing the accounts table. Added Cache-Control headers for edge caching.
Mobile App Updates:

useBlockchainWallet.ts — Now API-first with RPC fallback. Includes a source field so you can see which API served the data.
index.tsx (login screen) — Prefetches community feed, profile, and balance on login. Videos/thumbnails were already being prefetched.
videos.tsx — Look-ahead image prefetch for videos +2/+3/+4 ahead on swipe.
NOTE

The hbd_claimable field is set to '0' from the API because savings_hbd_seconds isn't available in HAFSQL. The RPC fallback provides the accurate value automatically.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Rebrands project to "Skatehive"; replaces legacy markdown pipeline with a tokenized Registry + provider embed system; adds UniversalRenderer, EmbedFactory, many embed providers/components; introduces AppSettings, FeedFilter, and ScrollLock contexts; implements session persistence, relationship caching, feed filtering, and unified conversation drawer flows.

Changes

Cohort / File(s) Summary
Project metadata & config
package.json, app.json, babel.config.js, .gitignore, types/env.d.ts, env.d.ts, README.md
Rename/version bump, Expo dependency updates, dotenv now loads .env.local, app display name and permission strings updated, env typings adjusted, README/docs rewritten.
Top-level docs
INSTRUCTIONS.md, SECURE_KEY_PRODUCTION_README.md, AGENTS.md, CLAUDE.md
Deleted legacy INSTRUCTIONS and SECURE_KEY_PRODUCTION_README; reorganized and condensed other docs.
App layout & providers
app/_layout.tsx, app/index.tsx, app/about.tsx
Added AppSettingsProvider & ScrollLockProvider; reworked splash/init; changed login navigation/prefetch behavior; updated About to use expo-web-browser.
Tabs & navigation
app/(tabs)/_layout.tsx, app/(tabs)/videos.tsx, app/(tabs)/profile.tsx, app/(tabs)/create.tsx, app/(tabs)/search.tsx
Feed filter integration, avatar tab icon, header filter dropdown, hidden search/notifications tabs, video comment drawer, Search screen added, media preview uses expo-image.
Auth & session
lib/auth-provider.tsx, components/auth/LoginForm.tsx, components/auth/StoredUsersView.tsx
Session persistence with expiry in SecureStore, AsyncStorage relationship caching/refresh, removed delete-user UI/props, simplified login flow.
App contexts
lib/AppSettingsContext.tsx, lib/FeedFilterContext.tsx, lib/ScrollLockContext.tsx
New public providers/hooks: AppSettings (persistent settings), FeedFilter (current filter), ScrollLock (global scroll lock).
Feed querying & API
lib/hooks/useQueries.ts, lib/hooks/useSnaps.ts, lib/hooks/useUserComments.ts, lib/hive-utils.ts, lib/api.ts
useSnaps now accepts filter/username and filters muted authors; paginated relationship retrieval; new API helpers for following/followers/muted/blacklisted; added prefetch helpers.
Resilient fetch & wallet
lib/resilient-fetch.ts, lib/hooks/useBlockchainWallet.ts
New resilientFetch (API-first, RPC fallback) with normalized balance/rewards; wallet hook exposes source indicator.
Markdown & embed system
lib/markdown/MarkdownProcessor.ts, lib/markdown/providers/*, lib/markdown/providers/index.ts, lib/markdown/providers/Registry.ts, components/markdown/UniversalRenderer.tsx, components/markdown/EmbedFactory.tsx, components/markdown/EnhancedMarkdownRenderer.tsx
Introduced MarkdownProcessor, Provider Registry, multiple MediaProviders (YouTube, Vimeo, Odysee, ThreeSpeak, IPFS, Image), UniversalRenderer and EmbedFactory; EnhancedMarkdownRenderer now delegates to UniversalRenderer.
Embed components
components/markdown/embeds/*
Added BaseVideoEmbed (WebView), ImageEmbed, InstagramEmbed, SnapshotEmbed, VideoEmbed, ZoraEmbed with lazy/placeholder and loading overlays.
Feed UI & conversation
components/Feed/...
Unified conversation drawer (visible→isVisible), PostCard adds isStatic/onOpenConversation and follow logic, Feed consumes filters and shared ConversationDrawer, FullConversationDrawer removed.
UI components & effects
components/ui/*
New GlobalHeader, SideMenu, CommentBottomSheet, VotePresetButtons; MatrixRain accepts container sizing; toast integrates MatrixRain; minor BadgedIcon tweak.
Profile, leaderboard, followers
app/(tabs)/profile.tsx, components/Profile/FollowersModal.tsx, components/Leaderboard/leaderboard.tsx, components/SpectatorMode/RewardsSpectatorInfo.tsx
Profile adds follow/mute flows and ConversationDrawer integration; FollowersModal uses new API functions; leaderboard filters muted users; small copy fixes.
Video playback & visibility
components/Feed/VideoWithAutoplay.tsx, lib/hooks/useInView.ts
Autoplay now requires screen focus and AppState active; visibility checks adjusted; reduced look-ahead prefetch.
Scripts & debug
debug-vitim.ts, scripts/*, scripts/...md
Added Hive debug utilities and docs describing modular lazy media rendering.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Feed as Feed Component
    participant Filter as FeedFilterContext
    participant Queries as useSnaps Hook
    participant API as API/RPC
    participant Renderer as UniversalRenderer
    participant Markdown as MarkdownProcessor
    participant Registry as Registry/Provider
    participant Provider as MediaProvider

    User->>Feed: Open feed
    Feed->>Filter: request current filter
    Filter-->>Feed: returns filter
    Feed->>Queries: fetch snaps(filter, username)
    Queries->>API: fetch discussions/containers
    API-->>Queries: raw posts
    Queries->>Queries: exclude muted authors
    Queries-->>Feed: posts
    Feed->>Renderer: render post.body (isVisible)
    Renderer->>Markdown: process content
    Markdown-->>Renderer: tokens ([[TYPE:ID]])
    Renderer->>Registry: get provider(TYPE)
    Registry-->>Renderer: provider component
    Renderer->>Provider: render Component(id, isVisible)
    Provider-->>Renderer: embed element
    Renderer-->>Feed: rendered post
Loading
sequenceDiagram
    participant App
    participant Auth as AuthProvider
    participant SecureStore
    participant AsyncStorage
    participant API as Relationships API

    App->>Auth: checkCurrentUser()
    Auth->>SecureStore: read SESSION_KEY
    SecureStore-->>Auth: session or null
    alt valid session and not auto-lock
        Auth->>AsyncStorage: load cached relationships
        AsyncStorage-->>Auth: cached lists
        Auth->>API: fetch fresh relationships
        API-->>Auth: updated lists
        Auth->>AsyncStorage: persist fresh lists
        Auth-->>App: authenticated
    else no/expired session or auto-lock enabled
        Auth->>SecureStore: clear SESSION_KEY
        Auth-->>App: unauthenticated
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇
I nibbled tokens from a markdown vine,
Registered providers, made embeds align,
Sessions tucked in SecureStore's cozy bed,
Filters hop; muted whispers stay unread,
Skatehive blooms — I twitch my whiskers, fed!

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

Copy link
Copy Markdown

@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: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
babel.config.js (1)

8-12: ⚠️ Potential issue | 🟠 Major

Hardcoded .env.local limits environment-specific configurations and will require refactoring for CI/CD setup.

While the current fallback URLs in lib/constants.ts prevent immediate failures, the hardcoded path cannot load environment-specific values for different deployment targets (dev/staging/prod). The .env.example defines configurable variables that should be used dynamically, not hardcoded to a single filename.

The suggested fix requires establishing an APP_ENV injection strategy (e.g., in CI/CD workflows or build scripts). Update babel.config.js to support environment-specific files:

Suggested fix
       ['dotenv-import', {
         moduleName: '@env',
-        path: '.env.local',
+        path: process.env.APP_ENV ? `.env.${process.env.APP_ENV}` : '.env',
         blacklist: null,
         whitelist: null,
-        safe: false,
-        allowUndefined: true,
+        safe: true,
+        allowUndefined: false,
       }],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@babel.config.js` around lines 8 - 12, The babel.config.js currently hardcodes
path: '.env.local' which prevents environment-specific configs; change the path
logic (the path property in babel.config.js) to derive the filename from an
injected APP_ENV (e.g., use process.env.APP_ENV to build
`.env.${process.env.APP_ENV}`) with sensible fallbacks (like `.env` or
`.env.local`) when APP_ENV is unset, and ensure CI/CD injects APP_ENV during
builds so environment-specific files are loaded rather than the fixed
'.env.local'.
components/notifications/NotificationItem.tsx (1)

112-240: ⚠️ Potential issue | 🟠 Major

Fetched parentDiscussion is never used, causing wasted network work.

The code builds/fetches parentDiscussion but never calls setPostData(parentDiscussion), so post stays undefined at Line 296 while still paying for extra requests.

Suggested fix
-          if (replyContent && replyContent.parent_author && replyContent.parent_permlink) {
+          if (replyContent && replyContent.parent_author && replyContent.parent_permlink) {
             setParentAuthor(replyContent.parent_author);
             setParentPermlink(replyContent.parent_permlink);
           } else if (postInfo) {
             setParentAuthor(postInfo.author);
             setParentPermlink(postInfo.permlink);
           }
+          setPostData(parentDiscussion);
           
           setIsConversationDrawerVisible(true);

Also applies to: 296-298

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

In `@components/notifications/NotificationItem.tsx` around lines 112 - 240, The
code fetches/builds parentDiscussion (via getContent and the fallback object)
but never assigns it to state, so add a call to setPostData(parentDiscussion)
after parentDiscussion is populated (i.e., in both the replyContent branch and
the fallback branch) before setIsConversationDrawerVisible(true); ensure this
occurs before using post/parent state so the component uses the fetched
discussion; reference symbols: parentDiscussion, getContent, setPostData,
setParentAuthor, setParentPermlink, setIsConversationDrawerVisible.
components/Profile/FollowersModal.tsx (1)

23-24: ⚠️ Potential issue | 🟠 Major

Add 'blacklisted' to the modal type union to enable the unreachable branch.

The else branch at lines 103–104 calls getBlacklistedList(), but the type prop at line 23 is constrained to 'followers' | 'following' | 'muted', making that branch unreachable under strict TypeScript. The header title logic at line 199 also hardcodes 'Muted' as its fallback, which would mislabel a blacklisted mode if it were somehow passed.

Update the type union and title logic to handle the blacklisted case:

Suggested fix
 interface FollowersModalProps {
   visible: boolean;
   onClose: () => void;
   username: string;
-  type: 'followers' | 'following' | 'muted';
+  type: 'followers' | 'following' | 'muted' | 'blacklisted';
 }
...
             <Text style={styles.title}>
-              {type === 'followers' ? 'Followers' : type === 'following' ? 'Following' : 'Muted'}
+              {type === 'followers'
+                ? 'Followers'
+                : type === 'following'
+                  ? 'Following'
+                  : type === 'muted'
+                    ? 'Muted'
+                    : 'Blacklisted'}
             </Text>

Also update the modalType state type in profile.tsx to include the new variant.

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

In `@components/Profile/FollowersModal.tsx` around lines 23 - 24, Update the modal
type union to include 'blacklisted' so the branch calling getBlacklistedList()
in FollowersModal is reachable: change the prop/type declaration currently
reading type: 'followers' | 'following' | 'muted' to also accept 'blacklisted',
update the header/title logic in FollowersModal (the fallback that currently
yields 'Muted') to explicitly render 'Blacklisted' when type === 'blacklisted'
(so the displayed title matches the mode), and also update the modalType state
type in profile.tsx to include 'blacklisted' so callers can open the modal with
that variant; ensure references to getBlacklistedList() remain unchanged.
components/ui/loading-effects/MatrixRain.tsx (1)

63-80: ⚠️ Potential issue | 🟡 Minor

Clamp the computed column count to at least one.

A very narrow containerWidth can make numCols equal 0, which makes columnPositions.length zero and turns drop.x into NaN. Guard this with Math.max(1, ...).

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

In `@components/ui/loading-effects/MatrixRain.tsx` around lines 63 - 80, The
calculation of numCols can produce 0 for very narrow finalWidth which makes
columnPositions empty and drop.x NaN; change the numCols computation used to
build columnPositions to clamp to at least 1 (e.g. use Math.max(1,
Math.floor(finalWidth / (BASE_FONT_SIZE * COLUMN_SPACING)))) so columnPositions
has at least one entry before mapping and when assigning x in rainDrops.current;
update any references that rely on numCols/columnPositions accordingly (numCols,
columnPositions, finalWidth, BASE_FONT_SIZE, COLUMN_SPACING).
lib/hooks/useBlockchainWallet.ts (1)

62-67: ⚠️ Potential issue | 🟡 Minor

Reset source when wallet data is cleared.

On Line 62 and Line 97, balanceData/rewardsData are reset but source is left unchanged, so consumers can read stale backend state after a clear/error.

Suggested patch
   if (!username || username === "SPECTATOR") {
     setBalanceData(null);
     setRewardsData(null);
+    setSource(null);
     setIsLoading(false);
     setError(null);
     return;
   }
@@
     } catch (err) {
       console.error("Error fetching blockchain wallet data:", err);
       setError(err instanceof Error ? err.message : "Failed to fetch wallet data");
       setBalanceData(null);
       setRewardsData(null);
+      setSource(null);
     } finally {
       setIsLoading(false);
     }

Also applies to: 97-102

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

In `@lib/hooks/useBlockchainWallet.ts` around lines 62 - 67, The clear/error paths
that call setBalanceData(null) and setRewardsData(null) must also reset the
wallet data source to avoid exposing stale backend state; update both the
username-guard branch (where setBalanceData, setRewardsData, setIsLoading,
setError are called) and the other clear/error branch (lines with
setBalanceData(null) / setRewardsData(null) around setIsLoading(false)) to also
call setSource(null) (or the corresponding setter for the source state) so
consumers cannot read an old source after a clear or error.
lib/api.ts (1)

44-50: ⚠️ Potential issue | 🟡 Minor

Fix malformed JSDoc comment.

The comment block starting at line 44 is incomplete and not properly closed, causing a syntax issue.

🐛 Proposed fix
-/**
- * Get balance
-
-
 /**
  * Fetches the Following feed
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/api.ts` around lines 44 - 50, The JSDoc block beginning with "Get
balance" is unterminated and merges with the next comment ("Fetches the
Following feed"); locate the malformed comment that starts with "/** Get
balance" and close it with "*/" (or split into two proper JSDoc blocks) so each
documentation block is properly terminated and does not interfere with the
subsequent "Fetches the Following feed" comment or surrounding code.
🟡 Minor comments (23)
agents.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Use h2 (##) instead of h3 (###) for proper heading hierarchy.

Markdown heading levels should increment by one level at a time. Since the previous heading is h1, the script descriptions should use h2.

📝 Proposed fix for heading levels
-### [debug-feed.ts](scripts/debug-feed.ts)
+## [debug-feed.ts](scripts/debug-feed.ts)
 Fetches recent blog posts for a specific user and analyzes the raw markdown for media patterns (Odysee, IPFS, YouTube).
 
-### [debug-snaps-specific.ts](scripts/debug-snaps-specific.ts)
+## [debug-snaps-specific.ts](scripts/debug-snaps-specific.ts)
 Specifically fetches "Snaps" (comments) using the `get_discussions_by_comments` API. This is crucial for debugging the Snaps feed since they are comments inside containers, not top-level posts.
 
 **Usage:**
 ```bash
 npx tsx scripts/debug-snaps-specific.ts

-### debug-problematic.ts
+## debug-problematic.ts
A temporary focus script to analyze specific authors like @skaters or @nogenta who are known to have complex media embeds.

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @agents.md at line 5, The markdown uses h3 (###) for script entries under the
top-level heading; update the script headings to h2 (##) so headings increment
by one level: change occurrences like "### debug-feed.ts" and "###
debug-problematic.ts" to "## debug-feed.ts" and "##
debug-problematic.ts" (also check any other script entries such as
"debug-snaps-specific.ts") to ensure consistent h1→h2 hierarchy.


</details>

</blockquote></details>
<details>
<summary>components/ui/CommentBottomSheet.tsx-128-146 (1)</summary><blockquote>

`128-146`: _⚠️ Potential issue_ | _🟡 Minor_

**Replace hardcoded color literals with theme tokens.**

Using `rgba(0,0,0,0.5)` and `#000` bypasses the project’s theme contract.



<details>
<summary>Proposed fix</summary>

```diff
   backdrop: {
     ...StyleSheet.absoluteFillObject,
-    backgroundColor: "rgba(0,0,0,0.5)",
+    backgroundColor: theme.colors.overlay, // add token in lib/theme.ts if missing
     zIndex: 100,
   },
@@
-    shadowColor: "#000",
+    shadowColor: theme.colors.background,

As per coding guidelines, "All colors and spacing must use the theme object from lib/theme.ts instead of hardcoded values".

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

In `@components/ui/CommentBottomSheet.tsx` around lines 128 - 146, The styles use
hardcoded colors in the backdrop, keyboardAvoidingView, and sheetContainer style
objects (rgba(0,0,0,0.5) and "#000"); update these to use the theme tokens from
the shared theme (e.g., theme.colors.* and theme.shadowColor or equivalent) so
colors follow the theme contract—replace the rgba backdrop color with the
appropriate theme token (semi‑transparent overlay token or create one in theme
if missing) and swap the sheetContainer shadowColor "#000" with
theme.colors.shadow (or the proper token name) and ensure any spacing uses
theme.spacing if needed.
components/ui/CommentBottomSheet.tsx-39-73 (1)

39-73: ⚠️ Potential issue | 🟡 Minor

Remove the ineffective visibility guard—it never prevents mounting.

The check at line 72 (slideAnim.addListener === undefined) is always false because React Native's Animated.Value always exposes the addListener method, making this branch unreachable and the component unable to conditionally unmount as intended.

Use explicit mount state tracking instead: add useState(isMounted), set it to true when isVisible becomes true, and set it to false after the hide animation completes (via the .start() callback), then replace the guard with if (!isMounted) return null;.

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

In `@components/ui/CommentBottomSheet.tsx` around lines 39 - 73, The visibility
guard using slideAnim.addListener is ineffective; replace it by adding a local
mount state (e.g., const [isMounted, setIsMounted] = useState(isVisible)), set
isMounted true when isVisible becomes true (inside the useEffect that runs the
show animation), and set isMounted false in the hide animation's
Animated.parallel(...).start(() => ...) callback after the hide animations
complete; then change the mount guard to if (!isMounted) return null; so
mounting/unmounting is driven by isMounted while keeping existing animations on
slideAnim and fadeAnim.
components/auth/StoredUsersView.tsx-23-24 (1)

23-24: ⚠️ Potential issue | 🟡 Minor

Normalize username once before rendering and actions.

You validate with trim(), but still use the raw username afterward. Whitespace-padded usernames can still generate bad avatar URLs and quick-login payloads.

✅ Suggested fix
-        {users
-          .filter(user => user.username && user.username.trim() !== "" && user.username !== "SPECTATOR")
-          .map((user, index) => (
+        {users
+          .map((user) => ({ ...user, username: user.username?.trim() ?? "" }))
+          .filter((user) => user.username !== "" && user.username !== "SPECTATOR")
+          .map((user, index) => (

Also applies to: 36-40

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

In `@components/auth/StoredUsersView.tsx` around lines 23 - 24, In
StoredUsersView.tsx normalize the username once inside the map/filter flow:
replace raw uses of user.username with a trimmed variable (e.g., const
normalizedUsername = user.username?.trim()) and use that variable for the filter
condition, avatar URL generation and quick-login payloads; ensure the filter
checks normalizedUsername !== "" and !== "SPECTATOR" and update all usages in
the map callback and the related render/actions (the callback parameter 'user'
and any avatar/quickLogin code around where .filter(...).map((user, index) =>
...) are implemented).
components/markdown/embeds/ZoraEmbed.tsx-14-14 (1)

14-14: ⚠️ Potential issue | 🟡 Minor

Encode the address before composing the Zora URL.

At Line 14, raw interpolation can produce malformed URLs for unexpected input. Use encodeURIComponent(address) when building the path.

Suggested fix
-  const zoraUrl = `https://zora.co/coin/${address}`;
+  const zoraUrl = `https://zora.co/coin/${encodeURIComponent(address)}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/markdown/embeds/ZoraEmbed.tsx` at line 14, The zora URL is built
without encoding, which can produce malformed links for special characters;
update the ZoraEmbed component so the template that defines zoraUrl uses an
encoded path segment by calling encodeURIComponent on address (i.e., replace the
raw interpolation used in zoraUrl with encodeURIComponent(address)) so the
composed URL is always valid.
app/(tabs)/search.tsx-14-19 (1)

14-19: ⚠️ Potential issue | 🟡 Minor

Add accessibility metadata to the search input.

At Line 14, add an accessibilityLabel (and optionally accessibilityHint) so screen-reader users can identify the field without relying on placeholder text.

As per coding guidelines, "Ensure components have appropriate accessibility labels".

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

In `@app/`(tabs)/search.tsx around lines 14 - 19, The TextInput in
app/(tabs)/search.tsx is missing accessibility metadata; add an
accessibilityLabel (e.g., accessibilityLabel="Search" or "Search Skatehive") and
optionally an accessibilityHint (e.g., "Enter keywords to search Skatehive") to
the TextInput component so screen readers can identify the field without relying
on the placeholder; update the TextInput props where it is declared (the
TextInput element with placeholder="Search Skatehive...") to include
accessibilityLabel and optionally accessibilityHint.
components/markdown/embeds/InstagramEmbed.tsx-18-28 (1)

18-28: ⚠️ Potential issue | 🟡 Minor

Add an explicit error state for failed embed loads.

If the embed fails, the component currently falls back to a blank area after loading ends. Show a user-friendly failure message in this state.

As per coding guidelines, "Handle loading states and errors gracefully, as demonstrated in LoadingScreen.tsx in components/ui".

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

In `@components/markdown/embeds/InstagramEmbed.tsx` around lines 18 - 28, The
component lacks an explicit error state for failed WebView loads: add an error
boolean (e.g., error / setError) and wire WebView's onError to setError(true)
(and onLoadStart to reset it), then conditionally render a user-friendly failure
UI when error === true (similar to LoadingScreen.tsx) in place of the blank
area—include a concise message, an icon or fallback thumbnail, and a retry
action that clears error and reloads the WebView; update references to loading,
setLoading, styles.loading, and the WebView props to implement this behavior.
scripts/lazy media rendering and modular media system.md-8-33 (1)

8-33: ⚠️ Potential issue | 🟡 Minor

Replace local file:///home/... links with repo-relative links.

Lines 8-33 currently point to a machine-specific path, so links will break for everyone else. Please switch these to repository-relative Markdown links (and use the correct AGENTS.md casing).

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

In `@scripts/lazy` media rendering and modular media system.md around lines 8 -
33, Update the machine-specific file:/// links in "scripts/lazy media rendering
and modular media system.md" to repository-relative Markdown links (e.g. replace
file:///.../lib/markdown/providers/Registry.ts#3-18 with a relative path like
lib/markdown/providers/Registry.ts and similarly for BaseProvider.ts,
BaseVideoEmbed.tsx, Feed.tsx, MarkdownProcessor.ts, UniversalRenderer.tsx and
the scripts/debug-*.ts entries) and correct the agents file casing from
agents.md to AGENTS.md wherever referenced; ensure each link uses repo-relative
paths and proper Markdown link syntax so they work for all contributors.
app/about.tsx-62-63 (1)

62-63: ⚠️ Potential issue | 🟡 Minor

Handle docs browser launch with proper async/error handling and loading state.

Line 62 calls WebBrowser.openBrowserAsync(...) without await or try-catch, allowing failures to be silent, and provides no visual feedback while the browser launches.

Suggested change
+import { useState } from 'react';
...
 export default function AboutScreen() {
+  const [openingDocs, setOpeningDocs] = useState(false);
+
+  const handleOpenDocs = async () => {
+    try {
+      setOpeningDocs(true);
+      await WebBrowser.openBrowserAsync('https://docs.skatehive.app/docs/');
+    } catch (error) {
+      console.error('Failed to open docs:', error);
+    } finally {
+      setOpeningDocs(false);
+    }
+  };
+
   return (
...
             <Pressable
-              onPress={() => WebBrowser.openBrowserAsync('https://docs.skatehive.app/docs/')}
+              onPress={handleOpenDocs}
+              disabled={openingDocs}
               style={[styles.actionButton, styles.primaryActionButton]}
             >
-              <Text style={styles.actionButtonText}>Read the Docs</Text>
+              <Text style={styles.actionButtonText}>
+                {openingDocs ? 'Opening…' : 'Read the Docs'}
+              </Text>
             </Pressable>

Per guidelines: use async/await for asynchronous operations, handle errors with try/catch blocks, and show loading states during async operations.

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

In `@app/about.tsx` around lines 62 - 63, Replace the inline onPress call that
invokes WebBrowser.openBrowserAsync with an async handler that awaits the call,
wraps it in try/catch to surface/log errors, and manages a local loading state
(e.g., isOpeningDocs / setIsOpeningDocs) so the UI can disable the button or
show a spinner while the browser launches; ensure the handler sets loading=true
before awaiting WebBrowser.openBrowserAsync and sets loading=false in finally,
and update the component to disable or render a loading indicator on the button
styled with styles.actionButton / styles.primaryActionButton while isOpeningDocs
is true.
components/ui/SideMenu.tsx-338-338 (1)

338-338: ⚠️ Potential issue | 🟡 Minor

This hidden-state guard never returns null.

slideAnim is always an Animated.Value, so slideAnim.addListener exists and this condition is effectively dead code. If the drawer should unmount after the close animation, track a separate render flag and clear it in the animation callback.

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

In `@components/ui/SideMenu.tsx` at line 338, The current guard in SideMenu using
"if (!isVisible && slideAnim.addListener === undefined) return null" is dead
because slideAnim is always an Animated.Value; replace this with a separate
render flag (e.g., isRendered state) that is set true when isVisible becomes
true and left true during the close animation, then cleared to false in the
animation completion callback or inside the slideAnim listener; update the
component to render based on isRendered while continuing to drive the animation
with isVisible and slideAnim so the drawer unmounts only after the close
animation finishes.
AGENTS.md-42-56 (1)

42-56: ⚠️ Potential issue | 🟡 Minor

Keep the chain invariants in this guide.

The shortened guide no longer mentions two rules the codebase still depends on: use hive-173115 as the primary community tag and peak.snaps as the Snaps container author. Those should stay in the blockchain conventions section so future agent changes don’t drift.

Based on learnings "Use the HIVE community hive-173115 (SkateHive) as the primary community tag" and "All blockchain operations must use the Snaps container author peak.snaps".

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

In `@AGENTS.md` around lines 42 - 56, Add the two missing chain invariants to the
blockchain conventions in AGENTS.md: explicitly require using the HIVE community
tag "hive-173115" and require the Snaps container author "peak.snaps" for all
blockchain operations; update the "Blockchain Operations" or "Key Conventions"
section near the existing items that reference lib/hive-utils.ts, try/catch for
RPC nodes, and decryptedKey from useAuth() so future agents follow these
invariants when calling functions in lib/hive-utils.ts and when constructing
Snaps interactions.
components/ui/toast.tsx-85-89 (1)

85-89: ⚠️ Potential issue | 🟡 Minor

Drive the Matrix overlay from the toast’s real size.

Only containerHeight={100} is passed here, so MatrixRain still computes columns from full-screen width and animates for a fixed height. Multi-line or differently sized toasts will render a clipped or sparse overlay. Measure the toast with onLayout and pass both width and height.

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

In `@components/ui/toast.tsx` around lines 85 - 89, The Matrix overlay is using a
fixed containerHeight (containerHeight={100}) and full-screen width which causes
clipping/sparse columns for multi-line or variable-sized toasts; update the
Toast component (where MatrixRain is rendered alongside styles.message and
styles.errorMessageText) to measure the toast's actual width and height via
onLayout on the toast container, store those dimensions in state, and pass them
into MatrixRain (e.g., containerWidth and containerHeight props) so the overlay
computes columns/animation from the toast's real size.
components/ui/GlobalHeader.tsx-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Remove as any type casts and add haptic feedback on interactive buttons.

The as any casts disable expo-router's route typing, turning a bad tab path into a runtime error instead of a compile-time error. Replace with typed Href constants. Additionally, add haptic feedback to the three interactive buttons (notifications, search, settings) using expo-haptics per component guidelines.

♻️ Proposed changes
-import { useRouter } from "expo-router";
+import { Href, useRouter } from "expo-router";
+import * as Haptics from "expo-haptics";
 import { Text } from "~/components/ui/text";
 import { theme } from "~/lib/theme";
 import { useAuth } from "~/lib/auth-provider";
 import useHiveAccount from "~/lib/hooks/useHiveAccount";
 import { SafeAreaView } from "react-native-safe-area-context";
 import { useNotificationContext } from "~/lib/notifications-context";
 import { BadgedIcon } from "./BadgedIcon";
 
+const SEARCH_ROUTE: Href = "/(tabs)/search";
+const NOTIFICATIONS_ROUTE: Href = "/(tabs)/notifications";
+
 const handleSearchPress = () => {
+  Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
-  router.push("/(tabs)/search" as any);
+  router.push(SEARCH_ROUTE);
 };
 
 const handleNotificationsPress = () => {
+  Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
-  router.push("/(tabs)/notifications" as any);
+  router.push(NOTIFICATIONS_ROUTE);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/GlobalHeader.tsx` at line 5, GlobalHeader contains unsafe `as
any` casts for route hrefs and lacks haptic feedback on interactive buttons;
remove the casts by declaring typed Href constants (e.g., const
NOTIFICATIONS_HREF: Href = "/notifications", const SEARCH_HREF: Href =
"/search", const SETTINGS_HREF: Href = "/settings") and use those constants with
useRouter()/Link instead of casting, and add haptic feedback by importing
expo-haptics and calling Haptics.selectionAsync() (or appropriate
Haptics.*Async) at the start of each button handler in the GlobalHeader
component (the notifications, search, and settings onPress/onPressIn handlers)
before performing navigation or other actions.
app/index.tsx-254-254 (1)

254-254: ⚠️ Potential issue | 🟡 Minor

Use theme color token instead of hardcoded background literal.

On Line 254, use theme.colors.background to keep color definitions centralized.

Suggested patch
   container: {
     flex: 1,
-    backgroundColor: "#000000",
+    backgroundColor: theme.colors.background,
   },
As per coding guidelines: "All colors and spacing must use the `theme` object from `lib/theme.ts`".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/index.tsx` at line 254, Replace the hardcoded backgroundColor literal
("#000000") in app/index.tsx with the theme token (theme.colors.background);
locate the style object where backgroundColor: "#000000" is set (e.g., in the
component or styles used by Index/App root) and swap the literal for
theme.colors.background, ensuring the theme is available by either importing the
theme from lib/theme.ts or using the existing theme hook/prop (e.g., useTheme or
ThemeProvider context) so the component references theme.colors.background
instead of the hardcoded string.
components/auth/LoginForm.tsx-259-260 (1)

259-260: ⚠️ Potential issue | 🟡 Minor

Restore visible loading feedback during authentication.

With Line 259 hardcoded to “Login” and Line 292 suppressing status text, there’s no clear loading cue while auth is in-flight.

Suggested patch
               <Button
                 onPress={handleSubmit}
                 style={[styles.button, styles.loginButton]}
                 disabled={isLoading}
               >
                 <Text style={styles.loginButtonText}>
-                  Login
+                  {isLoading ? "Authenticating..." : "Login"}
                 </Text>
               </Button>
As per coding guidelines: "Show loading states during async operations". Based on learnings: "Applies to **/components/**/*.{ts,tsx} : Handle loading states and errors gracefully, as demonstrated in LoadingScreen.tsx in components/ui".

Also applies to: 292-292

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

In `@components/auth/LoginForm.tsx` around lines 259 - 260, The LoginForm
currently renders a hardcoded "Login" label and hides the status text while
authentication is in-flight; restore visible loading feedback by using the
component's loading state (e.g., isLoading or authStatus) to conditionally
render/loading indicator and change the button label to "Logging in…" or show a
spinner when authenticateUser/handleSubmit is pending; also re-enable the status
text element (the status/statusText JSX) to display the current authStatus or
error messages (matching the LoadingScreen.tsx pattern) so users see progress
and errors during async auth calls in LoginForm.
components/Leaderboard/leaderboard.tsx-104-105 (1)

104-105: ⚠️ Potential issue | 🟡 Minor

Remove inline/error accent style literals; keep styling in StyleSheet with theme tokens.

On Line 104, inline style is used; on Line 241, color literal is hardcoded. Move both into named styles and use theme values.

Suggested patch
-          <Text style={[styles.matrixErrorText, { color: theme.colors.primary, marginTop: 10 }]}>
+          <Text style={styles.matrixErrorAccentText}>
             come back later to see our champions
           </Text>
@@
   matrixErrorText: {
     color: theme.colors.text,
     fontSize: theme.fontSizes.lg,
     fontFamily: theme.fonts.bold,
     textAlign: "center",
     textTransform: "lowercase",
     paddingHorizontal: theme.spacing.xl,
-    textShadowColor: "rgba(0, 255, 0, 0.5)",
+    textShadowColor: theme.colors.primary,
     textShadowOffset: { width: 0, height: 0 },
     textShadowRadius: 10,
   },
+  matrixErrorAccentText: {
+    color: theme.colors.primary,
+    marginTop: theme.spacing.sm,
+  },
As per coding guidelines: "Use `StyleSheet.create()` for styling — no inline styles" and "All colors and spacing must use the `theme` object from `lib/theme.ts`".

Also applies to: 234-243

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

In `@components/Leaderboard/leaderboard.tsx` around lines 104 - 105, The Text
element that currently uses an inline style (Text with styles.matrixErrorText
plus { color: theme.colors.primary, marginTop: 10 }) and the other hardcoded
color literal must be moved into StyleSheet.create and reference theme tokens;
create a new named style (e.g., matrixErrorAccent or matrixErrorPrimary) inside
the existing StyleSheet that sets color: theme.colors.primary and marginTop: 10,
and replace the inline style usage on the Text with styles.matrixErrorAccent
(keeping styles.matrixErrorText as-is); also replace the other hardcoded color
literal with that same style (or another named style that uses theme.colors.*)
so all color/spacing come from the StyleSheet and theme.
app/index.tsx-78-80 (1)

78-80: ⚠️ Potential issue | 🟡 Minor

Add explicit error handling for fire-and-forget prefetch calls.

On Line 78 and after successful logins (Line 120, Line 149), prefetch Promises are not handled. Wrap with void ...catch(...) (or Promise.allSettled) to avoid unhandled async failures.

Suggested patch
   React.useEffect(() => {
-    prefetchVideoFeed(queryClient);
-    prefetchCommunityFeed(queryClient);
-    warmUpVideoAssets(queryClient);
+    void Promise.allSettled([
+      prefetchVideoFeed(queryClient),
+      prefetchCommunityFeed(queryClient),
+      warmUpVideoAssets(queryClient),
+    ]);
   }, [queryClient]);
@@
-      prefetchProfile(queryClient, username);
-      prefetchBalance(queryClient, username);
+      void prefetchProfile(queryClient, username).catch((e) => {
+        console.error("prefetchProfile failed:", e);
+      });
+      void prefetchBalance(queryClient, username).catch((e) => {
+        console.error("prefetchBalance failed:", e);
+      });
@@
-      prefetchProfile(queryClient, selectedUsername);
-      prefetchBalance(queryClient, selectedUsername);
+      void prefetchProfile(queryClient, selectedUsername).catch((e) => {
+        console.error("prefetchProfile failed:", e);
+      });
+      void prefetchBalance(queryClient, selectedUsername).catch((e) => {
+        console.error("prefetchBalance failed:", e);
+      });
As per coding guidelines: "Use async/await with try/catch for asynchronous operations".

Also applies to: 120-121, 149-150

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

In `@app/index.tsx` around lines 78 - 80, The three fire-and-forget calls
prefetchVideoFeed, prefetchCommunityFeed, and warmUpVideoAssets are invoked
without handling rejections; update each invocation so returned Promises are
consumed and errors are caught—either wrap each call as void
prefetchX(...).catch(err => processLogger.error(...)) or group them into
Promise.allSettled([...]) and handle results, or convert the surrounding
function to async and await each call inside try/catch; ensure you update every
occurrence (the startup call and the post-login calls that also invoke these
functions) to avoid unhandled promise rejections and log the error with context
including the function name.
components/auth/LoginForm.tsx-271-276 (1)

271-276: ⚠️ Potential issue | 🟡 Minor

Harden spectator action for loading + accessibility.

On Line 271, add disabled gating and accessibility metadata to avoid overlapping auth actions and improve screen-reader support.

Suggested patch
           <Pressable
             onPress={onSpectator}
             style={styles.secondaryButton}
+            disabled={isLoading}
+            accessibilityRole="button"
+            accessibilityLabel="Enter as spectator"
           >
             <Text style={styles.secondaryButtonText}>Enter as Spectator</Text>
           </Pressable>
As per coding guidelines: "Ensure components have appropriate accessibility labels" and "Support screen readers via appropriate aria attributes".
lib/markdown/providers/IPFSProvider.tsx-18-21 (1)

18-21: ⚠️ Potential issue | 🟡 Minor

URL detection is fragile; consider using startsWith for robustness.

The check id.includes('https') could match IPFS hashes that happen to contain the substring "https". Using startsWith is safer.

-    const ipfsUrl = id.includes('https') ? id : `https://ipfs.skatehive.app/ipfs/${id}`;
+    const ipfsUrl = id.startsWith('https://') || id.startsWith('http://') ? id : `https://ipfs.skatehive.app/ipfs/${id}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/markdown/providers/IPFSProvider.tsx` around lines 18 - 21, The URL
detection in the Component is fragile because id.includes('https') can match
inside IPFS hashes; change the check that builds ipfsUrl in the Component to
verify that id actually begins with a URL (e.g., use id.startsWith('https') or a
stricter test like id.startsWith('http://') || id.startsWith('https://')) so
ipfsUrl is only treated as an absolute URL when id truly starts with a scheme;
keep the rest of the finalUrl logic and return of BaseVideoEmbed unchanged.
components/markdown/embeds/VideoEmbed.tsx-24-38 (1)

24-38: ⚠️ Potential issue | 🟡 Minor

Wrap switch case declarations in blocks to prevent scope leakage.

Static analysis correctly flags that let odyseeBase (line 25) and const ipfsUrl (line 37) can be erroneously accessed by other switch clauses.

🔧 Proposed fix
       case 'ODYSEE':
-        let odyseeBase = '';
-        if (id.includes('odysee.com/$/embed')) {
-          odyseeBase = id;
-        } else if (id.startsWith('http')) {
-           const match = id.match(/odysee\.com\/(?:[^\/]+\/)?([\w@:%._\+~#=\/-]+)/i);
-           const cleanId = match ? match[1] : id;
-           odyseeBase = `https://odysee.com/$/embed/${cleanId}`;
-        } else {
-          odyseeBase = `https://odysee.com/$/embed/${id}`;
-        }
-        return odyseeBase.includes('?') ? `${odyseeBase}&autoplay=false&muted=true` : `${odyseeBase}?autoplay=false&muted=true`;
-      case 'IPFSVIDEO':
-        const ipfsUrl = id.includes('https') ? id : `https://ipfs.skatehive.app/ipfs/${id}`;
-        return ipfsUrl.includes('?') ? `${ipfsUrl}&autoplay=0&muted=1` : `${ipfsUrl}?autoplay=0&muted=1`;
+        {
+          let odyseeBase = '';
+          if (id.includes('odysee.com/$/embed')) {
+            odyseeBase = id;
+          } else if (id.startsWith('http')) {
+            const match = id.match(/odysee\.com\/(?:[^\/]+\/)?([\w@:%._\+~#=\/-]+)/i);
+            const cleanId = match ? match[1] : id;
+            odyseeBase = `https://odysee.com/$/embed/${cleanId}`;
+          } else {
+            odyseeBase = `https://odysee.com/$/embed/${id}`;
+          }
+          return odyseeBase.includes('?') ? `${odyseeBase}&autoplay=false&muted=true` : `${odyseeBase}?autoplay=false&muted=true`;
+        }
+      case 'IPFSVIDEO': {
+        const ipfsUrl = id.startsWith('https://') || id.startsWith('http://') ? id : `https://ipfs.skatehive.app/ipfs/${id}`;
+        return ipfsUrl.includes('?') ? `${ipfsUrl}&autoplay=0&muted=1` : `${ipfsUrl}?autoplay=0&muted=1`;
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/markdown/embeds/VideoEmbed.tsx` around lines 24 - 38, The switch
cases 'ODYSEE' and 'IPFSVIDEO' leak variables (odyseeBase, ipfsUrl); wrap each
case body in its own block (e.g., case 'ODYSEE': { ... break } and case
'IPFSVIDEO': { ... break }) so odyseeBase and ipfsUrl are block-scoped and
cannot be accessed by other cases; ensure you keep the existing logic and
returns inside the new blocks and add a break at the end of each case block if
needed.
components/markdown/UniversalRenderer.tsx-74-82 (1)

74-82: ⚠️ Potential issue | 🟡 Minor

Memoize getStableKey to prevent unnecessary recalculations.

getStableKey is recreated on every render and included in the renderItems dependency array (line 125), causing renderItems to recalculate on every render and defeating the purpose of memoization.

♻️ Proposed fix: move function outside component or use useCallback
+// Move outside component since it has no dependencies
+const getStableKey = (str: string) => {
+  let hash = 0;
+  for (let i = 0; i < str.length; i++) {
+    hash = (hash << 5) - hash + str.charCodeAt(i);
+    hash |= 0;
+  }
+  return `part-${hash}`;
+};
+
 export const UniversalRenderer = ({ content, isVisible }: UniversalRendererProps) => {
   const processed = useMemo(() => MarkdownProcessor.process(content), [content]);
   // ...
-  // Simple hash for stable keys
-  const getStableKey = (str: string) => {
-    let hash = 0;
-    for (let i = 0; i < str.length; i++) {
-      hash = (hash << 5) - hash + str.charCodeAt(i);
-      hash |= 0;
-    }
-    return `part-${hash}`;
-  };
   // ...
   const renderItems = useMemo(() => {
     // ...
-  }, [parts, getStableKey]);
+  }, [parts]);

Also applies to: 125-125

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

In `@components/markdown/UniversalRenderer.tsx` around lines 74 - 82, getStableKey
is recreated on every render which forces renderItems to change (it's listed in
renderItems' dependency array), defeating memoization; fix by stabilizing
getStableKey — either move the getStableKey function definition outside the
component scope or wrap it in useCallback/useMemo inside the component and then
remove it as a volatile dependency so renderItems can remain memoized; refer to
the getStableKey and renderItems symbols when making the change.
lib/auth-provider.tsx-199-224 (1)

199-224: ⚠️ Potential issue | 🟡 Minor

getFollowersList result is fetched but never used.

The followers variable is retrieved in the Promise.all but is never stored or used. This is a wasteful API call.

🐛 Proposed fix — either use or remove

Option 1: Remove if not needed:

-const [following, muted, blacklisted, followers] = await Promise.all([
+const [following, muted, blacklisted] = await Promise.all([
   getFollowingList(targetUser),
   getMutedList(targetUser),
   getBlacklistedList(targetUser),
-  getFollowersList(targetUser),
 ]);

Option 2: If followers are needed for future use, store them:

+const [followersList, setFollowersList] = useState<string[]>([]);
...
 setFollowingList(following);
 setMutedList(muted);
 setBlacklistedList(blacklisted);
+setFollowersList(followers);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/auth-provider.tsx` around lines 199 - 224, The Promise.all includes
getFollowersList(targetUser) but its result (followers) is never used; either
remove getFollowersList from the Promise.all to avoid the wasted API call, or
persist/use the result by adding setFollowersList(followers) and include
followers in the saved cache (cacheDataToSave) and logs; locate the Promise.all
call and the subsequent state updates (setFollowingList, setMutedList,
setBlacklistedList) and update them accordingly and/or update the cacheKey
payload and console.log lines to include followers when choosing the second
option.
components/Feed/PostCard.tsx-56-57 (1)

56-57: ⚠️ Potential issue | 🟡 Minor

Remove the unused isStatic prop from PostCard.

The isStatic prop is declared in PostCardProps and destructured in the component signature but is never referenced in the component logic. Either remove the prop if it's not needed, or implement the intended behavior (likely to disable interactive features when the PostCard is used as a preview, as suggested by its usage in ConversationDrawer.tsx).

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

In `@components/Feed/PostCard.tsx` around lines 56 - 57, PostCardProps declares
and PostCard destructures an unused isStatic prop; remove isStatic from the
PostCardProps type and from the PostCard function parameter destructuring, and
delete any local references or defaulting related to it; also update any callers
(e.g., where ConversationDrawer previously passed isStatic) to stop passing that
prop. If instead the intended behavior is to make the card non-interactive when
static, implement the logic inside PostCard (check isStatic in the component and
conditionally disable click handlers, buttons and interactive elements) and keep
the prop only where used. Ensure references to the symbol names PostCardProps
and PostCard are updated consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9aa09d2-9ebb-4c0f-b8e1-cc76170e8b7e

📥 Commits

Reviewing files that changed from the base of the PR and between 202a85f and eb6bc64.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (76)
  • .gitignore
  • AGENTS.md
  • CLAUDE.md
  • INSTRUCTIONS.md
  • README.md
  • SECURE_KEY_PRODUCTION_README.md
  • agents.md
  • app.json
  • app/(tabs)/_layout.tsx
  • app/(tabs)/create.tsx
  • app/(tabs)/profile.tsx
  • app/(tabs)/search.tsx
  • app/(tabs)/videos.tsx
  • app/_layout.tsx
  • app/about.tsx
  • app/conversation.tsx
  • app/index.tsx
  • babel.config.js
  • components/Feed/Conversation.tsx
  • components/Feed/ConversationDrawer.tsx
  • components/Feed/Feed.tsx
  • components/Feed/FullConversationDrawer.tsx
  • components/Feed/MediaPreview.tsx
  • components/Feed/PostCard.tsx
  • components/Feed/VideoWithAutoplay.tsx
  • components/Leaderboard/leaderboard.tsx
  • components/Profile/FollowersModal.tsx
  • components/SpectatorMode/RewardsSpectatorInfo.tsx
  • components/auth/LoginForm.tsx
  • components/auth/StoredUsersView.tsx
  • components/markdown/EmbedFactory.tsx
  • components/markdown/EnhancedMarkdownRenderer.tsx
  • components/markdown/UniversalRenderer.tsx
  • components/markdown/embeds/BaseVideoEmbed.tsx
  • components/markdown/embeds/ImageEmbed.tsx
  • components/markdown/embeds/InstagramEmbed.tsx
  • components/markdown/embeds/SnapshotEmbed.tsx
  • components/markdown/embeds/VideoEmbed.tsx
  • components/markdown/embeds/ZoraEmbed.tsx
  • components/notifications/NotificationItem.tsx
  • components/ui/BadgedIcon.tsx
  • components/ui/CommentBottomSheet.tsx
  • components/ui/GlobalHeader.tsx
  • components/ui/SideMenu.tsx
  • components/ui/VotePresetButtons.tsx
  • components/ui/loading-effects/MatrixRain.tsx
  • components/ui/toast.tsx
  • debug-vitim.ts
  • lib/AppSettingsContext.tsx
  • lib/FeedFilterContext.tsx
  • lib/ScrollLockContext.tsx
  • lib/api.ts
  • lib/auth-provider.tsx
  • lib/constants.ts
  • lib/hive-utils.ts
  • lib/hooks/useBlockchainWallet.ts
  • lib/hooks/useInView.ts
  • lib/hooks/useQueries.ts
  • lib/hooks/useSnaps.ts
  • lib/hooks/useUserComments.ts
  • lib/markdown/MarkdownProcessor.ts
  • lib/markdown/providers/BaseProvider.ts
  • lib/markdown/providers/IPFSProvider.tsx
  • lib/markdown/providers/ImageProvider.tsx
  • lib/markdown/providers/OdyseeProvider.tsx
  • lib/markdown/providers/Registry.ts
  • lib/markdown/providers/ThreeSpeakProvider.tsx
  • lib/markdown/providers/VimeoProvider.tsx
  • lib/markdown/providers/YouTubeProvider.tsx
  • lib/markdown/providers/index.ts
  • lib/resilient-fetch.ts
  • package.json
  • scripts/debug-feed.ts
  • scripts/debug-snaps-specific.ts
  • scripts/lazy media rendering and modular media system.md
  • types/env.d.ts
💤 Files with no reviewable changes (3)
  • components/Feed/FullConversationDrawer.tsx
  • INSTRUCTIONS.md
  • SECURE_KEY_PRODUCTION_README.md

Remove Clipboard from react-native import—it's been removed in 0.81. Migrate to expo-clipboard with async error handling.

removed agents.md file

Create .env.example env.d.ts with all required environment variables.
Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (2)
app/_layout.tsx (1)

98-136: Remove duplicated inline contentStyle objects from Stack.Screen options.

contentStyle is already defined in Stack screenOptions (Line 101), so repeating the same inline object on each screen is redundant. Centralize it and keep only route-specific options (e.g., gestureEnabled for (tabs)).

♻️ Suggested refactor
 const styles = StyleSheet.create({
   container: {
     flex: 1,
     backgroundColor: theme.colors.background,
   },
+  stackContent: {
+    backgroundColor: theme.colors.background,
+  },
 });
@@
                         <View style={styles.container}>
                           <Stack
                             screenOptions={{
                               headerShown: false,
                               animation: 'none',
-                              contentStyle: { backgroundColor: theme.colors.background },
+                              contentStyle: styles.stackContent,
                             }}
                             initialRouteName="index"
                           >
-                            <Stack.Screen
-                              name="index"
-                              options={{
-                                contentStyle: { backgroundColor: theme.colors.background },
-                              }}
-                            />
-                            <Stack.Screen
-                              name="login"
-                              options={{
-                                contentStyle: { backgroundColor: theme.colors.background },
-                              }}
-                            />
-                            <Stack.Screen
-                              name="about"
-                              options={{
-                                contentStyle: { backgroundColor: theme.colors.background },
-                              }}
-                            />
-                            <Stack.Screen
-                              name="conversation"
-                              options={{
-                                contentStyle: { backgroundColor: theme.colors.background },
-                              }}
-                            />
+                            <Stack.Screen name="index" />
+                            <Stack.Screen name="login" />
+                            <Stack.Screen name="about" />
+                            <Stack.Screen name="conversation" />
                             <Stack.Screen
                               name="(tabs)"
                               options={{
                                 animation: 'none',
-                                contentStyle: { backgroundColor: theme.colors.background },
                                 gestureEnabled: false,
                               }}
                             />

As per coding guidelines, "Use StyleSheet.create() for styling — no inline styles, no NativeWind" and "All colors and spacing must use the theme object from lib/theme.ts instead of hardcoded values".

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

In `@app/_layout.tsx` around lines 98 - 136, Remove the duplicated inline
contentStyle objects from each Stack.Screen and rely on the top-level Stack
screenOptions.contentStyle; update only screen-specific options (e.g., keep
gestureEnabled for the "(tabs)" Stack.Screen and animation where needed). Move
any styling into a StyleSheet created via StyleSheet.create() and reference
colors/spacing from the theme object (lib/theme.ts) instead of inline values;
ensure no inline styles remain in Stack.Screen options and only pass
route-specific flags/overrides to the Stack.Screen components.
env.d.ts (1)

1-10: Consolidate duplicate @env module declarations into a single file.

Two TypeScript declarations of the @env module exist:

  • env.d.ts (root): Missing type annotation for LEADERBOARD_API_URL; includes unused MODERATOR_PUBLIC_KEY export
  • types/env.d.ts: Properly typed declarations

The codebase imports from @env only in lib/constants.ts and uses only API_BASE_URL and LEADERBOARD_API_URL. Keep the properly typed types/env.d.ts as the canonical source and remove the root env.d.ts to avoid confusion and type inconsistencies.

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

In `@env.d.ts` around lines 1 - 10, Remove the duplicate root declaration of
module '@env' and keep the canonical types/env.d.ts; ensure the retained
declaration exports API_BASE_URL and LEADERBOARD_API_URL with proper string
types (matching usage in lib/constants.ts) and remove unused exports such as
MODERATOR_PUBLIC_KEY from the root copy so there is only one authoritative,
correctly-typed module '@env' declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@env.d.ts`:
- Line 3: The exported constant LEADERBOARD_API_URL in env.d.ts lacks a
TypeScript type annotation; update the declaration of LEADERBOARD_API_URL to
include its type (string) so it reads as an exported string constant, ensuring
the file compiles under strict mode and matches the other env declarations.

---

Nitpick comments:
In `@app/_layout.tsx`:
- Around line 98-136: Remove the duplicated inline contentStyle objects from
each Stack.Screen and rely on the top-level Stack screenOptions.contentStyle;
update only screen-specific options (e.g., keep gestureEnabled for the "(tabs)"
Stack.Screen and animation where needed). Move any styling into a StyleSheet
created via StyleSheet.create() and reference colors/spacing from the theme
object (lib/theme.ts) instead of inline values; ensure no inline styles remain
in Stack.Screen options and only pass route-specific flags/overrides to the
Stack.Screen components.

In `@env.d.ts`:
- Around line 1-10: Remove the duplicate root declaration of module '@env' and
keep the canonical types/env.d.ts; ensure the retained declaration exports
API_BASE_URL and LEADERBOARD_API_URL with proper string types (matching usage in
lib/constants.ts) and remove unused exports such as MODERATOR_PUBLIC_KEY from
the root copy so there is only one authoritative, correctly-typed module '@env'
declaration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12fb81bd-30a3-41cb-9dd4-85c35bf757c2

📥 Commits

Reviewing files that changed from the base of the PR and between eb6bc64 and 7b70938.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • .gitignore
  • app/_layout.tsx
  • components/ui/SideMenu.tsx
  • env.d.ts
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/ui/SideMenu.tsx

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (1)
types/env.d.ts (1)

5-5: Consider typing MODERATOR_PUBLIC_KEY as optional.

The runtime code in lib/hive-utils.ts explicitly guards against undefined (if (!MODERATOR_PUBLIC_KEY) on line 1115) and re-exports without a fallback, indicating this variable may not always be set. Typing it as string bypasses compile-time null checks elsewhere.

♻️ Proposed fix
-  export const MODERATOR_PUBLIC_KEY: string;
+  export const MODERATOR_PUBLIC_KEY: string | undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@types/env.d.ts` at line 5, The declaration for MODERATOR_PUBLIC_KEY should be
made optional to match runtime guards; update the type in types/env.d.ts from a
required string to an optional string (e.g., `string | undefined`) so callers
and the re-exporting logic that checks `if (!MODERATOR_PUBLIC_KEY)` (see usages
around the guard in hive-utils) get proper compile-time nullability; ensure any
code that expects a value still narrows/checks before use.
🤖 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/_layout.tsx`:
- Around line 25-27: The top-level useEffect call using
SplashScreen.preventAutoHideAsync is invalid at module scope; move that effect
into the RootLayout React component (the function/component named RootLayout)
and change it to an async effect that awaits the SplashScreen calls inside a
try/catch block to handle errors; specifically, call await
SplashScreen.preventAutoHideAsync() (and any subsequent SplashScreen.hideAsync()
where appropriate) within the effect and log or handle errors in the catch so
the splash-screen promises are properly awaited and exceptions are not
unhandled.

---

Nitpick comments:
In `@types/env.d.ts`:
- Line 5: The declaration for MODERATOR_PUBLIC_KEY should be made optional to
match runtime guards; update the type in types/env.d.ts from a required string
to an optional string (e.g., `string | undefined`) so callers and the
re-exporting logic that checks `if (!MODERATOR_PUBLIC_KEY)` (see usages around
the guard in hive-utils) get proper compile-time nullability; ensure any code
that expects a value still narrows/checks before use.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 85dd5285-dde4-4371-81a9-e75c2eaa0456

📥 Commits

Reviewing files that changed from the base of the PR and between 7b70938 and 2c4940d.

📒 Files selected for processing (3)
  • app/_layout.tsx
  • env.d.ts
  • types/env.d.ts
💤 Files with no reviewable changes (1)
  • env.d.ts

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
app/_layout.tsx (1)

105-121: Re-add contentStyle to Stack screenOptions to enforce dark theme background during transitions.

The Stack configuration is missing the contentStyle property. React Navigation's native-stack navigator uses an opaque default background during route transitions when contentStyle is not explicitly set, which can briefly surface a non-dark background. Adding contentStyle: { backgroundColor: theme.colors.background } ensures the dark theme background is consistently enforced.

🎨 Suggested adjustment
                            <Stack
                              screenOptions={{
                                headerShown: false,
                                animation: 'none',
+                               contentStyle: { backgroundColor: theme.colors.background },
                              }}
                              initialRouteName="index"
                            >

Aligns with guidelines: "Use dark theme only" and "All colors must come from lib/theme.ts."

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

In `@app/_layout.tsx` around lines 105 - 121, The Stack navigator's screenOptions
is missing contentStyle, causing non-dark backgrounds during transitions; update
the screenOptions passed to Stack (the object with headerShown, animation,
initialRouteName and used by Stack.Screen declarations) to include contentStyle:
{ backgroundColor: theme.colors.background } so the dark background from
lib/theme.ts is enforced during transitions; ensure you import or reference the
same theme instance used elsewhere so all colors come from
theme.colors.background.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/_layout.tsx`:
- Around line 105-121: The Stack navigator's screenOptions is missing
contentStyle, causing non-dark backgrounds during transitions; update the
screenOptions passed to Stack (the object with headerShown, animation,
initialRouteName and used by Stack.Screen declarations) to include contentStyle:
{ backgroundColor: theme.colors.background } so the dark background from
lib/theme.ts is enforced during transitions; ensure you import or reference the
same theme instance used elsewhere so all colors come from
theme.colors.background.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 70af7f4e-b808-46c9-99a1-b53a16d6ee64

📥 Commits

Reviewing files that changed from the base of the PR and between 2c4940d and a300646.

📒 Files selected for processing (1)
  • app/_layout.tsx

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
app/_layout.tsx (1)

72-86: Splash screen handling is functional but calls preventAutoHideAsync on every re-render.

The critical issue from the past review (module-scope useEffect) has been fixed — the effect is now inside RootLayout with proper try/catch. However, the merged effect calls preventAutoHideAsync() each time fontsLoaded changes (once when false, once when true). While Expo handles repeated calls gracefully, separating the concerns would be cleaner.

♻️ Optional: Separate prevent and hide effects
 export default function RootLayout() {
   const [fontsLoaded] = useFonts({
     'FiraCode-Regular': FiraCode_400Regular,
     'FiraCode-Bold': FiraCode_700Bold,
   });

+  // Prevent auto-hide once on mount
+  useEffect(() => {
+    SplashScreen.preventAutoHideAsync().catch((error) =>
+      console.warn('Splash prevent error:', error)
+    );
+  }, []);
+
+  // Hide when fonts are ready
   useEffect(() => {
-    const init = async () => {
-      try {
-        await SplashScreen.preventAutoHideAsync();
-
-        if (fontsLoaded) {
-          await SplashScreen.hideAsync();
-        }
-      } catch (error) {
-        console.warn("Splash error:", error);
+    if (fontsLoaded) {
+      SplashScreen.hideAsync().catch((error) =>
+        console.warn('Splash hide error:', error)
+      );
-      }
-    };
-
-    init();
+    }
   }, [fontsLoaded]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/_layout.tsx` around lines 72 - 86, The current useEffect in RootLayout
calls SplashScreen.preventAutoHideAsync() every time fontsLoaded changes; split
this into two effects: one run once on mount to call preventAutoHideAsync() (use
an effect with empty dependency array) and a separate effect that watches
fontsLoaded to call SplashScreen.hideAsync() when fontsLoaded becomes true.
Update the existing init logic by moving preventAutoHideAsync into a mount-only
effect and keeping the try/catch around each SplashScreen call (or at least
around hideAsync in the fontsLoaded effect) to preserve the existing error
handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/_layout.tsx`:
- Around line 72-86: The current useEffect in RootLayout calls
SplashScreen.preventAutoHideAsync() every time fontsLoaded changes; split this
into two effects: one run once on mount to call preventAutoHideAsync() (use an
effect with empty dependency array) and a separate effect that watches
fontsLoaded to call SplashScreen.hideAsync() when fontsLoaded becomes true.
Update the existing init logic by moving preventAutoHideAsync into a mount-only
effect and keeping the try/catch around each SplashScreen call (or at least
around hideAsync in the fontsLoaded effect) to preserve the existing error
handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 338ff1e7-58c6-4a5f-9e45-8cbe1e830bca

📥 Commits

Reviewing files that changed from the base of the PR and between a300646 and f9f4004.

📒 Files selected for processing (1)
  • app/_layout.tsx

SplashScreen.preventAutoHideAsync() every time fontsLoaded changes; split this
into two effects: one run once on mount to call preventAutoHideAsync()  and a separate effect that watches
fontsLoaded
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.

2 participants