feat: introduce core infrastructure (media, cache, API)#10
feat: introduce core infrastructure (media, cache, API)#10rferrari wants to merge 47 commits intoSkateHive:mainfrom
Conversation
…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.
…n has been disabled.
…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.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRebrands 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 | 🟠 MajorHardcoded
.env.locallimits environment-specific configurations and will require refactoring for CI/CD setup.While the current fallback URLs in
lib/constants.tsprevent immediate failures, the hardcoded path cannot load environment-specific values for different deployment targets (dev/staging/prod). The.env.exampledefines configurable variables that should be used dynamically, not hardcoded to a single filename.The suggested fix requires establishing an
APP_ENVinjection strategy (e.g., in CI/CD workflows or build scripts). Updatebabel.config.jsto 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 | 🟠 MajorFetched
parentDiscussionis never used, causing wasted network work.The code builds/fetches
parentDiscussionbut never callssetPostData(parentDiscussion), sopoststaysundefinedat 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 | 🟠 MajorAdd
'blacklisted'to the modal type union to enable the unreachable branch.The
elsebranch at lines 103–104 callsgetBlacklistedList(), but thetypeprop 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
modalTypestate 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 | 🟡 MinorClamp the computed column count to at least one.
A very narrow
containerWidthcan makenumColsequal0, which makescolumnPositions.lengthzero and turnsdrop.xintoNaN. Guard this withMath.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 | 🟡 MinorReset
sourcewhen wallet data is cleared.On Line 62 and Line 97,
balanceData/rewardsDataare reset butsourceis 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 | 🟡 MinorFix 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 | 🟡 MinorUse 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@skatersor@nogentawho 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.mdat 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
themeobject fromlib/theme.tsinstead 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 | 🟡 MinorRemove the ineffective visibility guard—it never prevents mounting.
The check at line 72 (
slideAnim.addListener === undefined) is always false because React Native'sAnimated.Valuealways exposes theaddListenermethod, making this branch unreachable and the component unable to conditionally unmount as intended.Use explicit mount state tracking instead: add
useState(isMounted), set it totruewhenisVisiblebecomes true, and set it tofalseafter the hide animation completes (via the.start()callback), then replace the guard withif (!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 | 🟡 MinorNormalize 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 | 🟡 MinorEncode 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 | 🟡 MinorAdd accessibility metadata to the search input.
At Line 14, add an
accessibilityLabel(and optionallyaccessibilityHint) 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 | 🟡 MinorAdd 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 | 🟡 MinorReplace 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.mdcasing).🤖 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 | 🟡 MinorHandle docs browser launch with proper async/error handling and loading state.
Line 62 calls
WebBrowser.openBrowserAsync(...)withoutawaitortry-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 | 🟡 MinorThis hidden-state guard never returns
null.
slideAnimis always anAnimated.Value, soslideAnim.addListenerexists 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 | 🟡 MinorKeep the chain invariants in this guide.
The shortened guide no longer mentions two rules the codebase still depends on: use
hive-173115as the primary community tag andpeak.snapsas 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 authorpeak.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 | 🟡 MinorDrive the Matrix overlay from the toast’s real size.
Only
containerHeight={100}is passed here, soMatrixRainstill 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 withonLayoutand 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 | 🟡 MinorRemove
as anytype casts and add haptic feedback on interactive buttons.The
as anycasts disable expo-router's route typing, turning a bad tab path into a runtime error instead of a compile-time error. Replace with typedHrefconstants. Additionally, add haptic feedback to the three interactive buttons (notifications, search, settings) usingexpo-hapticsper 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 | 🟡 MinorUse theme color token instead of hardcoded background literal.
On Line 254, use
theme.colors.backgroundto keep color definitions centralized.As per coding guidelines: "All colors and spacing must use the `theme` object from `lib/theme.ts`".Suggested patch
container: { flex: 1, - backgroundColor: "#000000", + backgroundColor: theme.colors.background, },🤖 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 | 🟡 MinorRestore 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.
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".Suggested patch
<Button onPress={handleSubmit} style={[styles.button, styles.loginButton]} disabled={isLoading} > <Text style={styles.loginButtonText}> - Login + {isLoading ? "Authenticating..." : "Login"} </Text> </Button>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 | 🟡 MinorRemove inline/error accent style literals; keep styling in
StyleSheetwith 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.
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`".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, + },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 | 🟡 MinorAdd 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(...)(orPromise.allSettled) to avoid unhandled async failures.As per coding guidelines: "Use async/await with try/catch for asynchronous operations".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); + });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 | 🟡 MinorHarden spectator action for loading + accessibility.
On Line 271, add
disabledgating and accessibility metadata to avoid overlapping auth actions and improve screen-reader support.As per coding guidelines: "Ensure components have appropriate accessibility labels" and "Support screen readers via appropriate aria attributes".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>lib/markdown/providers/IPFSProvider.tsx-18-21 (1)
18-21:⚠️ Potential issue | 🟡 MinorURL detection is fragile; consider using
startsWithfor robustness.The check
id.includes('https')could match IPFS hashes that happen to contain the substring "https". UsingstartsWithis 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 | 🟡 MinorWrap switch case declarations in blocks to prevent scope leakage.
Static analysis correctly flags that
let odyseeBase(line 25) andconst 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 | 🟡 MinorMemoize
getStableKeyto prevent unnecessary recalculations.
getStableKeyis recreated on every render and included in therenderItemsdependency array (line 125), causingrenderItemsto 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
getFollowersListresult is fetched but never used.The
followersvariable is retrieved in thePromise.allbut 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 | 🟡 MinorRemove the unused
isStaticprop from PostCard.The
isStaticprop is declared inPostCardPropsand 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 inConversationDrawer.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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (76)
.gitignoreAGENTS.mdCLAUDE.mdINSTRUCTIONS.mdREADME.mdSECURE_KEY_PRODUCTION_README.mdagents.mdapp.jsonapp/(tabs)/_layout.tsxapp/(tabs)/create.tsxapp/(tabs)/profile.tsxapp/(tabs)/search.tsxapp/(tabs)/videos.tsxapp/_layout.tsxapp/about.tsxapp/conversation.tsxapp/index.tsxbabel.config.jscomponents/Feed/Conversation.tsxcomponents/Feed/ConversationDrawer.tsxcomponents/Feed/Feed.tsxcomponents/Feed/FullConversationDrawer.tsxcomponents/Feed/MediaPreview.tsxcomponents/Feed/PostCard.tsxcomponents/Feed/VideoWithAutoplay.tsxcomponents/Leaderboard/leaderboard.tsxcomponents/Profile/FollowersModal.tsxcomponents/SpectatorMode/RewardsSpectatorInfo.tsxcomponents/auth/LoginForm.tsxcomponents/auth/StoredUsersView.tsxcomponents/markdown/EmbedFactory.tsxcomponents/markdown/EnhancedMarkdownRenderer.tsxcomponents/markdown/UniversalRenderer.tsxcomponents/markdown/embeds/BaseVideoEmbed.tsxcomponents/markdown/embeds/ImageEmbed.tsxcomponents/markdown/embeds/InstagramEmbed.tsxcomponents/markdown/embeds/SnapshotEmbed.tsxcomponents/markdown/embeds/VideoEmbed.tsxcomponents/markdown/embeds/ZoraEmbed.tsxcomponents/notifications/NotificationItem.tsxcomponents/ui/BadgedIcon.tsxcomponents/ui/CommentBottomSheet.tsxcomponents/ui/GlobalHeader.tsxcomponents/ui/SideMenu.tsxcomponents/ui/VotePresetButtons.tsxcomponents/ui/loading-effects/MatrixRain.tsxcomponents/ui/toast.tsxdebug-vitim.tslib/AppSettingsContext.tsxlib/FeedFilterContext.tsxlib/ScrollLockContext.tsxlib/api.tslib/auth-provider.tsxlib/constants.tslib/hive-utils.tslib/hooks/useBlockchainWallet.tslib/hooks/useInView.tslib/hooks/useQueries.tslib/hooks/useSnaps.tslib/hooks/useUserComments.tslib/markdown/MarkdownProcessor.tslib/markdown/providers/BaseProvider.tslib/markdown/providers/IPFSProvider.tsxlib/markdown/providers/ImageProvider.tsxlib/markdown/providers/OdyseeProvider.tsxlib/markdown/providers/Registry.tslib/markdown/providers/ThreeSpeakProvider.tsxlib/markdown/providers/VimeoProvider.tsxlib/markdown/providers/YouTubeProvider.tsxlib/markdown/providers/index.tslib/resilient-fetch.tspackage.jsonscripts/debug-feed.tsscripts/debug-snaps-specific.tsscripts/lazy media rendering and modular media system.mdtypes/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.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/_layout.tsx (1)
98-136: Remove duplicated inlinecontentStyleobjects fromStack.Screenoptions.
contentStyleis already defined inStackscreenOptions(Line 101), so repeating the same inline object on each screen is redundant. Centralize it and keep only route-specific options (e.g.,gestureEnabledfor(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 thethemeobject fromlib/theme.tsinstead 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@envmodule declarations into a single file.Two TypeScript declarations of the
@envmodule exist:
env.d.ts(root): Missing type annotation forLEADERBOARD_API_URL; includes unusedMODERATOR_PUBLIC_KEYexporttypes/env.d.ts: Properly typed declarationsThe codebase imports from
@envonly inlib/constants.tsand uses onlyAPI_BASE_URLandLEADERBOARD_API_URL. Keep the properly typedtypes/env.d.tsas the canonical source and remove the rootenv.d.tsto 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
.gitignoreapp/_layout.tsxcomponents/ui/SideMenu.tsxenv.d.tspackage.json
🚧 Files skipped from review as they are similar to previous changes (1)
- components/ui/SideMenu.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
types/env.d.ts (1)
5-5: Consider typingMODERATOR_PUBLIC_KEYas optional.The runtime code in
lib/hive-utils.tsexplicitly 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 asstringbypasses 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
📒 Files selected for processing (3)
app/_layout.tsxenv.d.tstypes/env.d.ts
💤 Files with no reviewable changes (1)
- env.d.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/_layout.tsx (1)
105-121: Re-addcontentStyleto Stack screenOptions to enforce dark theme background during transitions.The Stack configuration is missing the
contentStyleproperty. React Navigation's native-stack navigator uses an opaque default background during route transitions whencontentStyleis not explicitly set, which can briefly surface a non-dark background. AddingcontentStyle: { 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.
…nds during transitions
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/_layout.tsx (1)
72-86: Splash screen handling is functional but callspreventAutoHideAsyncon every re-render.The critical issue from the past review (module-scope
useEffect) has been fixed — the effect is now insideRootLayoutwith propertry/catch. However, the merged effect callspreventAutoHideAsync()each timefontsLoadedchanges (once whenfalse, once whentrue). 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.
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
🎯 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+UniversalRendererfor modular multimedia renderingMarkdownProcessorfor content parsingLazy Media & Provider System
BaseVideoEmbedcomponentEnvironment-based API URLs
.envenvironment variablesCore Mute Filtering
getMutedList,getBlacklistedList,getFollowingList,getFollowersListinapi.tsresilient-fetchdependency)Followers Cache Enhancement
auth-providerPhase 0 (Cache) + Phase 1 (Wallet)
🔧 Conflict Resolution Notes
6386cbc(Mute Filtering)profile.tsx,leaderboard.tsx10a8392(API muted list)resilient-fetch.ts10a8392(API muted list)api.ts10a8392(API muted list)auth-provider.tsx9b89a11(Followers cache)auth-provider.tsx🧪 Testing
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Documentation