feat: add Lobstr wallet, dynamic portfolio comparison, OG meta tags, … - #1615
Conversation
…and embed widget params
|
Someone is attempting to deploy a commit to the ritik4ever's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@yadavaman8960 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughChangesThe frontend adds Lobstr wallet detection, expands portfolio comparison to five selections, introduces size/theme query parameters for the embed widget, and injects Open Graph metadata for shared portfolios. Vitest coverage validates these behaviors, with a Lobstr wallet support
Portfolio comparison expansion
Configurable embed widget
Public portfolio metadata
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant EmbedWidget
participant URLSearchParams
participant api
participant Chart
EmbedWidget->>URLSearchParams: read size and theme
URLSearchParams-->>EmbedWidget: validated widget configuration
EmbedWidget->>api: load portfolio data
api-->>EmbedWidget: portfolio and asset data
EmbedWidget->>Chart: render size-based chart geometry
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Biome (2.5.5)frontend/src/utils/walletAdapters.tsFile contains syntax errors that prevent linting: Line 220: Expected a statement but instead found '} Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
frontend/src/components/WalletPicker.test.tsx (2)
16-27: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winDo not mock the production wallet list for this assertion.
Because
SUPPORTED_WALLETSis replaced with a hand-written mock, the “renders all supported wallet options” test can pass even if the real configuration omits Lobstr. Use the production list or add a focused configuration test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/WalletPicker.test.tsx` around lines 16 - 27, Update the WalletPicker test setup to preserve the production SUPPORTED_WALLETS list instead of replacing it with a hand-written mock. Mock only the wallet state helpers such as getLastUsedWallet, setLastUsedWallet, and clearLastUsedWallet, so the “renders all supported wallet options” assertion validates the real configuration, including Lobstr.
57-80: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd adapter-level Lobstr tests.
These tests use an empty provider object and mock
walletManager.connect, so they never exerciseLobstrAdapter.connect,isConnected,signTransaction, ordisconnect. Add unit tests for those adapter methods and their failure paths; otherwise the picker tests can pass while the wallet integration remains broken.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/WalletPicker.test.tsx` around lines 57 - 80, Add focused unit tests for LobstrAdapter covering connect, isConnected, signTransaction, and disconnect, including each method’s failure paths. Use a realistic mocked Lobstr provider and assert the adapter’s provider calls, returned values, and propagated or handled errors; keep the existing WalletPicker tests unchanged.frontend/src/pages/__tests__/PublicPortfolio.test.tsx (1)
32-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest the complete metadata lifecycle.
The test only partially checks the OG contract and its
afterEachmanually deletes tags, so it does not verify component cleanup. Assertdocument.title,og:type,og:site_name, the exact URL, andog:image; unmount the component and verify the previous title/tags are restored.Also applies to: 60-80
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/pages/__tests__/PublicPortfolio.test.tsx` around lines 32 - 36, Update the PublicPortfolio metadata test around the component render and afterEach cleanup to cover the complete lifecycle: assert document.title, og:type, og:site_name, the exact URL, and og:image values; unmount the rendered component, then verify the pre-existing title and metadata tags are restored. Remove the manual OG-tag deletion from afterEach so the test validates component cleanup rather than masking it.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/pages/__tests__/Compare.test.tsx`:
- Around line 57-66: Add a sixth portfolio fixture to mockPortfolios, then
update the existing selection test to click every card and assert the count
remains “5/5 selected,” ensuring the sixth selection is rejected. Keep the test
focused on the Compare component’s selection limit.
- Around line 68-86: Strengthen the test around Compare’s three-portfolio
selection by asserting mockUsePortfolioCompare receives all three selected
portfolio IDs, not merely that it was called. Also assert the rendered
comparison table includes headers for Growth Portfolio, Income Portfolio, and
Balanced Portfolio so the test detects any two-selection cap.
In `@frontend/src/pages/Compare.tsx`:
- Line 41: Cap portfolio IDs initialized from the URL to five before they are
stored or passed to usePortfolioCompare. Update the URL parsing/initialization
logic near the selectedPortfolioIds setup, while preserving the existing
selectedPortfolioIds.length click guard and ensuring the displayed count cannot
exceed 5.
In `@frontend/src/pages/EmbedWidget.tsx`:
- Around line 105-113: Update the loading and error/unavailable branches in
EmbedWidget to use the existing isDark value when selecting background and text
styling, instead of relying only on Tailwind dark: classes. Preserve the current
successful-data behavior and add regression coverage verifying dark-theme
loading and error rendering.
In `@frontend/src/pages/PublicPortfolio.tsx`:
- Around line 6-22: Update setMetaTags and removeMetaTags to preserve each tag’s
pre-existing state: capture prior title/content values before overwriting, track
which meta elements were created by this component, restore captured values
during cleanup, and remove only component-created tags. Ensure the cleanup also
restores the original document title instead of using a hard-coded replacement.
- Around line 70-82: The PublicPortfolio page currently injects
portfolio-specific metadata only inside the client-side useEffect, so crawlers
receive generic tags. Move this metadata generation into the
server-rendered/HTML response path using the fetched portfolio data, and add an
absolute og:image preview URL while preserving the existing title, description,
URL, type, and site name values.
In `@frontend/src/utils/stellar.ts`:
- Around line 20-24: The LOBSTR integration uses the wrong provider global and
API. In frontend/src/utils/stellar.ts lines 20-24,
frontend/src/utils/walletAdapters.ts lines 218-245 and 254-265, and
frontend/src/components/WalletPicker.tsx line 30, replace window.lobstr and
requestAccess with window.lobstrSignerExtensionApi, using getPublicKey(),
isConnected(), and signTransaction(xdr) for detection, connection, and signing
while preserving the existing adapter and picker behavior.
---
Nitpick comments:
In `@frontend/src/components/WalletPicker.test.tsx`:
- Around line 16-27: Update the WalletPicker test setup to preserve the
production SUPPORTED_WALLETS list instead of replacing it with a hand-written
mock. Mock only the wallet state helpers such as getLastUsedWallet,
setLastUsedWallet, and clearLastUsedWallet, so the “renders all supported wallet
options” assertion validates the real configuration, including Lobstr.
- Around line 57-80: Add focused unit tests for LobstrAdapter covering connect,
isConnected, signTransaction, and disconnect, including each method’s failure
paths. Use a realistic mocked Lobstr provider and assert the adapter’s provider
calls, returned values, and propagated or handled errors; keep the existing
WalletPicker tests unchanged.
In `@frontend/src/pages/__tests__/PublicPortfolio.test.tsx`:
- Around line 32-36: Update the PublicPortfolio metadata test around the
component render and afterEach cleanup to cover the complete lifecycle: assert
document.title, og:type, og:site_name, the exact URL, and og:image values;
unmount the rendered component, then verify the pre-existing title and metadata
tags are restored. Remove the manual OG-tag deletion from afterEach so the test
validates component cleanup rather than masking it.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6ee4d41a-f318-492d-a42b-b9710020ebca
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (12)
frontend/package.jsonfrontend/src/components/WalletPicker.test.tsxfrontend/src/components/WalletPicker.tsxfrontend/src/pages/Compare.tsxfrontend/src/pages/EmbedWidget.tsxfrontend/src/pages/PublicPortfolio.tsxfrontend/src/pages/__tests__/Compare.test.tsxfrontend/src/pages/__tests__/EmbedWidget.test.tsxfrontend/src/pages/__tests__/PublicPortfolio.test.tsxfrontend/src/test/setup.tsfrontend/src/utils/stellar.tsfrontend/src/utils/walletAdapters.ts
| it('allows selecting up to 5 portfolios and shows correct count', () => { | ||
| render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />) | ||
|
|
||
| const allCards = mockPortfolios.map(p => screen.getByText(p.name).closest('div')) | ||
| for (const card of allCards) { | ||
| if (card) fireEvent.click(card) | ||
| } | ||
|
|
||
| expect(screen.getByText('5/5 selected')).toBeTruthy() | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Test rejection of a sixth selection.
This test has only five cards, so it still passes if the limit changes to six. Add a sixth fixture, click all six cards, and keep the expected count at 5/5 selected.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/__tests__/Compare.test.tsx` around lines 57 - 66, Add a
sixth portfolio fixture to mockPortfolios, then update the existing selection
test to click every card and assert the count remains “5/5 selected,” ensuring
the sixth selection is rejected. Keep the test focused on the Compare
component’s selection limit.
| it('renders comparison table for 3 portfolios', async () => { | ||
| render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />) | ||
|
|
||
| const card1 = screen.getByText('Growth Portfolio').closest('div') | ||
| const card2 = screen.getByText('Income Portfolio').closest('div') | ||
| const card3 = screen.getByText('Balanced Portfolio').closest('div') | ||
| if (card1) fireEvent.click(card1) | ||
| if (card2) fireEvent.click(card2) | ||
| if (card3) fireEvent.click(card3) | ||
|
|
||
| await waitFor(() => { | ||
| expect(mockUsePortfolioCompare).toHaveBeenCalled() | ||
| expect(screen.getByText('Total Return')).toBeTruthy() | ||
| expect(screen.getByText('Volatility')).toBeTruthy() | ||
| expect(screen.getByText('Max Drawdown')).toBeTruthy() | ||
| expect(screen.getByText('Sharpe Ratio')).toBeTruthy() | ||
| expect(screen.getByText('Rebalance Count')).toBeTruthy() | ||
| expect(screen.getByText('Portfolio Value')).toBeTruthy() | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Verify that all three selected IDs drive the comparison.
The mocked response always contains three portfolios, so this passes even if selection is capped at two. Assert the hook receives the three IDs and that the table has all three portfolio headers.
Proposed assertions
await waitFor(() => {
- expect(mockUsePortfolioCompare).toHaveBeenCalled()
+ expect(mockUsePortfolioCompare).toHaveBeenLastCalledWith([
+ 'portfolio-1',
+ 'portfolio-2',
+ 'portfolio-3',
+ ])
expect(screen.getByText('Total Return')).toBeTruthy()
})
+
+expect(screen.getByRole('columnheader', { name: 'Growth Portfolio' })).toBeTruthy()
+expect(screen.getByRole('columnheader', { name: 'Income Portfolio' })).toBeTruthy()
+expect(screen.getByRole('columnheader', { name: 'Balanced Portfolio' })).toBeTruthy()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('renders comparison table for 3 portfolios', async () => { | |
| render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />) | |
| const card1 = screen.getByText('Growth Portfolio').closest('div') | |
| const card2 = screen.getByText('Income Portfolio').closest('div') | |
| const card3 = screen.getByText('Balanced Portfolio').closest('div') | |
| if (card1) fireEvent.click(card1) | |
| if (card2) fireEvent.click(card2) | |
| if (card3) fireEvent.click(card3) | |
| await waitFor(() => { | |
| expect(mockUsePortfolioCompare).toHaveBeenCalled() | |
| expect(screen.getByText('Total Return')).toBeTruthy() | |
| expect(screen.getByText('Volatility')).toBeTruthy() | |
| expect(screen.getByText('Max Drawdown')).toBeTruthy() | |
| expect(screen.getByText('Sharpe Ratio')).toBeTruthy() | |
| expect(screen.getByText('Rebalance Count')).toBeTruthy() | |
| expect(screen.getByText('Portfolio Value')).toBeTruthy() | |
| }) | |
| it('renders comparison table for 3 portfolios', async () => { | |
| render(<Compare onNavigate={onNavigate} publicKey={'GA-test-key'} />) | |
| const card1 = screen.getByText('Growth Portfolio').closest('div') | |
| const card2 = screen.getByText('Income Portfolio').closest('div') | |
| const card3 = screen.getByText('Balanced Portfolio').closest('div') | |
| if (card1) fireEvent.click(card1) | |
| if (card2) fireEvent.click(card2) | |
| if (card3) fireEvent.click(card3) | |
| await waitFor(() => { | |
| expect(mockUsePortfolioCompare).toHaveBeenLastCalledWith([ | |
| 'portfolio-1', | |
| 'portfolio-2', | |
| 'portfolio-3', | |
| ]) | |
| expect(screen.getByText('Total Return')).toBeTruthy() | |
| expect(screen.getByText('Volatility')).toBeTruthy() | |
| expect(screen.getByText('Max Drawdown')).toBeTruthy() | |
| expect(screen.getByText('Sharpe Ratio')).toBeTruthy() | |
| expect(screen.getByText('Rebalance Count')).toBeTruthy() | |
| expect(screen.getByText('Portfolio Value')).toBeTruthy() | |
| }) | |
| expect(screen.getByRole('columnheader', { name: 'Growth Portfolio' })).toBeTruthy() | |
| expect(screen.getByRole('columnheader', { name: 'Income Portfolio' })).toBeTruthy() | |
| expect(screen.getByRole('columnheader', { name: 'Balanced Portfolio' })).toBeTruthy() | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/__tests__/Compare.test.tsx` around lines 68 - 86,
Strengthen the test around Compare’s three-portfolio selection by asserting
mockUsePortfolioCompare receives all three selected portfolio IDs, not merely
that it was called. Also assert the rendered comparison table includes headers
for Growth Portfolio, Income Portfolio, and Balanced Portfolio so the test
detects any two-selection cap.
| if (selectedPortfolioIds.includes(portfolioId)) { | ||
| setSelectedPortfolioIds(prev => prev.filter(id => id !== portfolioId)) | ||
| } else if (selectedPortfolioIds.length < 3) { | ||
| } else if (selectedPortfolioIds.length < 5) { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Cap URL-initialized selections as well.
Line 41 only limits clicks. ?portfolios=a,b,c,d,e,f initializes six IDs at Lines 20-23, displays 6/5, and sends all six to usePortfolioCompare.
Proposed fix
const COLORS = ['`#3B82F6`', '`#10B981`', '`#F59E0B`', '`#EF4444`', '`#8B5CF6`', '`#EC4899`']
+const MAX_SELECTED_PORTFOLIOS = 5
const [selectedPortfolioIds, setSelectedPortfolioIds] = useState<string[]>(() => {
const params = searchParams.get('portfolios')
- return params ? params.split(',') : []
+ return [...new Set(params?.split(',').filter((id) => id.length > 0) ?? [])]
+ .slice(0, MAX_SELECTED_PORTFOLIOS)
})
- } else if (selectedPortfolioIds.length < 5) {
+ } else if (selectedPortfolioIds.length < MAX_SELECTED_PORTFOLIOS) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } else if (selectedPortfolioIds.length < 5) { | |
| } else if (selectedPortfolioIds.length < MAX_SELECTED_PORTFOLIOS) { |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/Compare.tsx` at line 41, Cap portfolio IDs initialized
from the URL to five before they are stored or passed to usePortfolioCompare.
Update the URL parsing/initialization logic near the selectedPortfolioIds setup,
while preserving the existing selectedPortfolioIds.length click guard and
ensuring the displayed count cannot exceed 5.
| <div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center m-0 p-0 overflow-hidden ${s.container}`}> | ||
| <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" /> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| if (error || !data) { | ||
| return ( | ||
| <div className="h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center p-4 text-center m-0 overflow-hidden"> | ||
| <div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center p-4 text-center m-0 overflow-hidden ${s.container}`}> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Honor theme in loading and unavailable states.
theme=dark only affects the successful-data branch. These branches rely on Tailwind dark: mode, so a dark embed can render light (and vice versa) until data loads or after an error. Use isDark here too, and add a regression test for dark loading/error rendering.
Proposed fix
- <div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center m-0 p-0 overflow-hidden ${s.container}`}>
+ <div className={`h-screen w-full ${isDark ? 'bg-slate-950' : 'bg-white'} flex items-center justify-center m-0 p-0 overflow-hidden ${s.container}`}>
- <div className={`h-screen w-full bg-white dark:bg-slate-950 flex items-center justify-center p-4 text-center m-0 overflow-hidden ${s.container}`}>
- <p className="text-sm text-slate-500 dark:text-slate-400 font-medium">
+ <div className={`h-screen w-full ${isDark ? 'bg-slate-950' : 'bg-white'} flex items-center justify-center p-4 text-center m-0 overflow-hidden ${s.container}`}>
+ <p className={`${s.asset} ${isDark ? 'text-slate-400' : 'text-slate-500'} font-medium`}>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/EmbedWidget.tsx` around lines 105 - 113, Update the
loading and error/unavailable branches in EmbedWidget to use the existing isDark
value when selecting background and text styling, instead of relying only on
Tailwind dark: classes. Preserve the current successful-data behavior and add
regression coverage verifying dark-theme loading and error rendering.
| function setMetaTags(tags: Record<string, string>) { | ||
| for (const [property, content] of Object.entries(tags)) { | ||
| let el = document.querySelector(`meta[property="${property}"]`) | ||
| if (!el) { | ||
| el = document.createElement('meta') | ||
| el.setAttribute('property', property) | ||
| document.head.appendChild(el) | ||
| } | ||
| el.setAttribute('content', content) | ||
| } | ||
| } | ||
|
|
||
| function removeMetaTags(properties: string[]) { | ||
| for (const property of properties) { | ||
| const el = document.querySelector(`meta[property="${property}"]`) | ||
| if (el) el.remove() | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Restore existing head state during cleanup.
setMetaTags overwrites any existing global tags, but cleanup always removes them and replaces the prior title with a hard-coded value. Capture the previous title/content and remove only tags created by this component; otherwise navigating away can erase metadata owned by the app shell or another route.
Also applies to: 83-87
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/PublicPortfolio.tsx` around lines 6 - 22, Update
setMetaTags and removeMetaTags to preserve each tag’s pre-existing state:
capture prior title/content values before overwriting, track which meta elements
were created by this component, restore captured values during cleanup, and
remove only component-created tags. Ensure the cleanup also restores the
original document title instead of using a hard-coded replacement.
| useEffect(() => { | ||
| if (!data || metaInjected.current) return | ||
| metaInjected.current = true | ||
| const totalValue = data.portfolio.totalValue?.toLocaleString() || '0' | ||
| const assetCount = Object.keys(data.portfolio.allocations || {}).length | ||
| document.title = `Portfolio Snapshot — ${totalValue} | Stellar Portfolio Rebalancer` | ||
| setMetaTags({ | ||
| 'og:title': `Portfolio Snapshot — $${totalValue}`, | ||
| 'og:description': `Shared portfolio with ${assetCount} asset${assetCount !== 1 ? 's' : ''}. Total value: $${totalValue}.`, | ||
| 'og:url': window.location.href, | ||
| 'og:type': 'website', | ||
| 'og:site_name': 'Stellar Portfolio Rebalancer', | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg '(^|/)PublicPortfolio\.tsx$|package\.json$|vite\.config|next\.config|ssr|server|middleware|app\.router|i18n' || true
echo
echo "PublicPortfolio outline:"
fd -a 'PublicPortfolio\.tsx' . | while read -r f; do
echo "--- $f"
wc -l "$f"
ast-grep outline "$f" --view compact || true
done
echo
echo "Relevant sections:"
fd -a 'PublicPortfolio\.tsx' . | while read -r f; do
echo "--- $f lines 1-160"
cat -n "$f" | sed -n '1,160p'
done
echo
echo "Meta tag usages:"
rg -n "setMetaTags|metaInjected|PublicPortfolio|og:image|Property\('<meta|Head|Head" frontend src app . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' | head -200Repository: ritik4ever/stellar-portfolio-rebalancer
Length of output: 27522
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "frontend/package.json:"
cat -n frontend/package.json
echo
echo "frontend/vite.config.ts:"
cat -n frontend/vite.config.ts
echo
echo "frontend/src/App.tsx route/public-share section:"
cat -n frontend/src/App.tsx | sed -n '400,455p'
echo
echo "frontend/src/config/api.ts:"
cat -n frontend/src/config/api.ts | sed -n '1,140p'
echo
echo "backend routing for share endpoints:"
rg -n "PORTFOLIO_SHARE|share|public.*portfolio|/:hash|hash" backend/src frontend/src -g '*.ts' -g '*.tsx' | head -200Repository: ritik4ever/stellar-portfolio-rebalancer
Length of output: 36834
Render share metadata in the HTML response and add og:image.
This share page fetches the portfolio after mount, so initial HTTP crawlers that don’t execute this effect receive generic/no portfolio Open Graph metadata. Move the portfolio-specific tags into the crawler-facing HTML/SSR response and add an absolute og:image preview URL.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/pages/PublicPortfolio.tsx` around lines 70 - 82, The
PublicPortfolio page currently injects portfolio-specific metadata only inside
the client-side useEffect, so crawlers receive generic tags. Move this metadata
generation into the server-rendered/HTML response path using the fetched
portfolio data, and add an absolute og:image preview URL while preserving the
existing title, description, URL, type, and site name values.
Source: MCP tools
| lobstr?: { | ||
| requestAccess(): Promise<{ publicKey: string }> | ||
| signTransaction(xdr: string, opts?: { networkPassphrase?: string }): Promise<string> | ||
| isConnected(): Promise<boolean> | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
github.com/Lobstrco/lobstr-browser-extension @lobstrco/signer-extension-api window.lobstrSignerExtensionApi getPublicKey signTransaction xdr
💡 Result:
The @lobstrco/signer-extension-api is a JavaScript library designed to facilitate communication between web applications and the LOBSTR browser extension for the Stellar network [1][2]. When installed or included via a script tag, the library exposes a global object named window.lobstrSignerExtensionApi that allows web applications to interact directly with the user's extension [1][2][3]. Key methods available on the window.lobstrSignerExtensionApi object include: isConnected: Returns a promise that resolves to a boolean, indicating whether the LOBSTR signer extension is installed and accessible [1][2][3]. getPublicKey: Requests the user's public key from the extension [1][2]. signTransaction(xdr): This method accepts a Stellar transaction XDR string, which the extension decodes, prompts the user to sign, and subsequently returns the signed transaction to the application [1][2][3]. signMessage(message): Accepts a message string, which the extension signs and returns along with the signer's address [1][2][3]. Developers can integrate this API by installing the npm package @lobstrco/signer-extension-api or by using a CDN-hosted script tag [1][2]. Once available, the API methods are called directly from the window.lobstrSignerExtensionApi global variable [2][3].
Citations:
- 1: https://github.com/Lobstrco/lobstr-browser-extension/tree/main/%40lobstrco/signer-extension-api
- 2: https://www.npmjs.com/package/@lobstrco/signer-extension-api
- 3: https://cdn.jsdelivr.net/npm/@lobstrco/signer-extension-api@2.0.0/README.md
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg 'frontend/(src/utils/(stellar|walletAdapters)\.(ts|tsx)|src/components/WalletPicker\.tsx)$'
echo "== stellar.ts =="
cat -n frontend/src/utils/stellar.ts | sed -n '1,80p'
echo "== walletAdapters Lobstr section =="
cat -n frontend/src/utils/walletAdapters.ts | sed -n '190,275p'
echo "== WalletPicker section =="
cat -n frontend/src/components/WalletPicker.tsx | sed -n '1,80p'Repository: ritik4ever/stellar-portfolio-rebalancer
Length of output: 9647
Use the LOBSTR signer-extension global/methods instead of window.lobstr.
LOBBSTR exposes window.lobstrSignerExtensionApi with getPublicKey(), isConnected(), and signTransaction(xdr); the current window.lobstr.requestAccess() contract is not the installed-provider API, so LOBSTR detection, connection, and signing will fail in the standard flow. Update frontend/src/utils/stellar.ts, frontend/src/utils/walletAdapters.ts, and frontend/src/components/WalletPicker.tsx to use that global and signature.
📍 Affects 3 files
frontend/src/utils/stellar.ts#L20-L24(this comment)frontend/src/utils/walletAdapters.ts#L218-L245frontend/src/utils/walletAdapters.ts#L254-L265frontend/src/components/WalletPicker.tsx#L30-L30
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@frontend/src/utils/stellar.ts` around lines 20 - 24, The LOBSTR integration
uses the wrong provider global and API. In frontend/src/utils/stellar.ts lines
20-24, frontend/src/utils/walletAdapters.ts lines 218-245 and 254-265, and
frontend/src/components/WalletPicker.tsx line 30, replace window.lobstr and
requestAccess with window.lobstrSignerExtensionApi, using getPublicKey(),
isConnected(), and signTransaction(xdr) for detection, connection, and signing
while preserving the existing adapter and picker behavior.
closes #1443
closes #1444
closes #1445
closes #1449
Summary by CodeRabbit