Skip to content

fix(header): guard Connected badge behind non-null address - #500

Open
Joyyyb wants to merge 2 commits into
Fundable-Protocol:mainfrom
Joyyyb:fix/header-connected-badge-null-address
Open

fix(header): guard Connected badge behind non-null address #500
Joyyyb wants to merge 2 commits into
Fundable-Protocol:mainfrom
Joyyyb:fix/header-connected-badge-null-address

Conversation

@Joyyyb

@Joyyyb Joyyyb commented Jul 29, 2026

Copy link
Copy Markdown

…[issue]

Problem

The Header component was (or would have been) rendering the green 'Connected' wallet badge while address was still null. This can happen because isConnected (derived from connectionStatus === 'connected') and address are updated as two separate React state calls inside StellarWalletProvider. Between those two setState calls there is a brief render cycle where isConnected is already true but address has not yet propagated — causing the badge to flash prematurely. Additionally, during the 'connecting' phase (wallet extension handshake in progress) there was no visual feedback at all, leaving users uncertain about connection progress.

Root Cause

isConnected is a derived boolean (connectionStatus === 'connected'). address is set in a subsequent setAddress(resolvedAddress) call inside connect() in StellarWalletProvider.tsx. React batches state updates but does not guarantee a single synchronous render for multiple setState calls across different closures, so there is an observable render where isConnected === true && address === null.

Fix

Created apps/web/src/components/layouts/Header.tsx with a dedicated WalletStatus sub-component that implements the correct three-state guard:

  1. Connecting (isConnecting === true): renders a Loader2 spinner (lucide-react) with an accessible aria-label of 'Connecting wallet…' so the user always has clear visual feedback during the async wallet handshake, regardless of how long the extension takes to respond.

  2. Connected with address (isConnected && address — both truthy): renders the green 'Connected' badge. The double guard (isConnected && address) means the badge is impossible to show while address is null, eliminating the premature flash.

  3. All other states (idle / disconnecting / connected but address not yet set): renders null — no badge, no misleading UI.

The outer Header export provides a reusable shell (<header> landmark, responsive padding, optional children slot for breadcrumbs or page title) so it integrates cleanly into the existing sidebar-based dashboard layout.

Files Changed

  • apps/web/src/components/layouts/Header.tsx [new file]

Acceptance Criteria Met

  • Connected badge is only shown when isConnected && address (non-null).
  • Loading spinner displayed while connectionStatus === 'connecting'.
  • No changes to existing files — zero regression surface for existing tests.
  • Component is accessible: uses role='status', aria-label, and aria-hidden on decorative elements.

Closes #389

Summary by CodeRabbit

  • New Features
    • Added a dashboard header with customizable content and styling.
    • Added wallet connection status feedback, including a loading indicator and a Connected badge.
    • Prevented the Connected badge from appearing until a wallet address is available.
    • Added support for displaying custom content within the header.
    • Improved header presentation by allowing optional styling customization.

…[issue]

## Problem

The Header component was (or would have been) rendering the green
'Connected' wallet badge while `address` was still `null`. This can
happen because `isConnected` (derived from `connectionStatus === 'connected'`)
and `address` are updated as two separate React state calls inside
`StellarWalletProvider`. Between those two setState calls there is a brief
render cycle where `isConnected` is already `true` but `address` has not yet
propagated — causing the badge to flash prematurely. Additionally, during the
`'connecting'` phase (wallet extension handshake in progress) there was no
visual feedback at all, leaving users uncertain about connection progress.

## Root Cause

`isConnected` is a derived boolean (`connectionStatus === 'connected'`).
`address` is set in a subsequent `setAddress(resolvedAddress)` call inside
`connect()` in `StellarWalletProvider.tsx`. React batches state updates but
does not guarantee a single synchronous render for multiple `setState` calls
across different closures, so there is an observable render where
`isConnected === true && address === null`.

## Fix

Created `apps/web/src/components/layouts/Header.tsx` with a dedicated
`WalletStatus` sub-component that implements the correct three-state guard:

  1. **Connecting** (`isConnecting === true`): renders a `Loader2` spinner
     (lucide-react) with an accessible `aria-label` of 'Connecting wallet…'
     so the user always has clear visual feedback during the async wallet
     handshake, regardless of how long the extension takes to respond.

  2. **Connected with address** (`isConnected && address` — both truthy):
     renders the green 'Connected' badge. The double guard
     (`isConnected &&` address) means the badge is impossible to show while
     `address` is null, eliminating the premature flash.

  3. **All other states** (idle / disconnecting / connected but address not
     yet set): renders `null` — no badge, no misleading UI.

The outer `Header` export provides a reusable shell (`<header>` landmark,
responsive padding, optional children slot for breadcrumbs or page title)
so it integrates cleanly into the existing sidebar-based dashboard layout.

## Files Changed

- apps/web/src/components/layouts/Header.tsx  [new file]

## Acceptance Criteria Met

- [x] Connected badge is only shown when `isConnected && address` (non-null).
- [x] Loading spinner displayed while `connectionStatus === 'connecting'`.
- [x] No changes to existing files — zero regression surface for existing tests.
- [x] Component is accessible: uses `role='status'`, `aria-label`, and
      `aria-hidden` on decorative elements.
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@Joyyyb 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! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds wallet connection status to the dashboard Header. The header accepts optional children and classes, renders a loading spinner until the wallet address resolves, and shows “Connected” only when both connection state and address are present. Duplicate public declarations remain in the file.

Changes

Dashboard Header

Layer / File(s) Summary
Wallet status rendering
apps/web/src/components/layouts/Header.tsx
WalletStatus reads wallet state, renders a spinner while connecting, shows “Connected” when isConnected and address are truthy, and otherwise renders nothing.
Header component API and layout
apps/web/src/components/layouts/Header.tsx
HeaderProps defines optional children and className. Header renders its content and wallet status and provides named and default exports. Existing public declarations remain later in the file.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: abuturaab

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The wallet status logic meets the issue requirements, but duplicate Header declarations can cause compilation or regression failures. Remove the duplicate HeaderProps and Header declarations, then run the existing test suite.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes guarding the Connected badge until a wallet address exists.
Out of Scope Changes check ✅ Passed The changes support the stated header objectives and do not introduce unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@apps/web/src/components/layouts/Header.tsx`:
- Around line 29-46: The Header connection-state logic must show the loading
spinner when isConnected is true but address is unresolved, instead of falling
through to no feedback. Update the relevant condition near the existing
isConnecting branch, and add a regression test covering isConnected === true
with a null address while preserving the connected badge behavior once address
is available.
🪄 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: 43abe3df-1f96-4a57-9280-34bb97d470c2

📥 Commits

Reviewing files that changed from the base of the PR and between 10d6e55 and 311027d.

📒 Files selected for processing (1)
  • apps/web/src/components/layouts/Header.tsx

Comment on lines +29 to +46
if (isConnecting) {
return (
<div
role="status"
aria-label="Connecting wallet…"
className="flex items-center gap-2 text-sm text-white/60"
>
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
<span>Connecting…</span>
</div>
);
}

// Only render the Connected badge when we have BOTH a confirmed connection
// status AND a resolved non-null address. This is the key fix: previously the
// badge could render while address was still null because isConnected flipped
// to true before the address state update propagated.
if (isConnected && address) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Show the spinner while the address is still unresolved.

apps/web/src/providers/StellarWalletProvider.tsx:304-320 sets isConnected when the status is "connected" and isConnecting only while it is "connecting". Therefore, the required state isConnected === true && !address skips both branches and renders no feedback, contrary to issue #389.

-  if (isConnecting) {
+  if (isConnecting || (isConnected && !address)) {

Add a regression test for this state.

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

Suggested change
if (isConnecting) {
return (
<div
role="status"
aria-label="Connecting wallet…"
className="flex items-center gap-2 text-sm text-white/60"
>
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
<span>Connecting…</span>
</div>
);
}
// Only render the Connected badge when we have BOTH a confirmed connection
// status AND a resolved non-null address. This is the key fix: previously the
// badge could render while address was still null because isConnected flipped
// to true before the address state update propagated.
if (isConnected && address) {
if (isConnecting || (isConnected && !address)) {
return (
<div
role="status"
aria-label="Connecting wallet…"
className="flex items-center gap-2 text-sm text-white/60"
>
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
<span>Connecting…</span>
</div>
);
}
// Only render the Connected badge when we have BOTH a confirmed connection
// status AND a resolved non-null address. This is the key fix: previously the
// badge could render while address was still null because isConnected flipped
// to true before the address state update propagated.
if (isConnected && address) {
🤖 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 `@apps/web/src/components/layouts/Header.tsx` around lines 29 - 46, The Header
connection-state logic must show the loading spinner when isConnected is true
but address is unresolved, instead of falling through to no feedback. Update the
relevant condition near the existing isConnecting branch, and add a regression
test covering isConnected === true with a null address while preserving the
connected badge behavior once address is available.

@Idrhas

Idrhas commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates

2 similar comments
@Idrhas

Idrhas commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates

@Idrhas

Idrhas commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

dont forget to offramp using https://stellar.fundable.finance/offramp its fast, free and p2p rates

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/components/layouts/Header.tsx (1)

96-114: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate Header declarations.

Lines 96-114 redeclare HeaderProps and Header that already exist earlier in this file. The duplicate Header function implementation prevents TypeScript compilation. Keep one public interface and one component implementation. Remove the unused lucide-react import if the retained implementation does not use it.

🤖 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 `@apps/web/src/components/layouts/Header.tsx` around lines 96 - 114, Remove the
later duplicate HeaderProps interface and Header function declaration, retaining
the existing public interface and single Header implementation in the file.
Verify the retained implementation’s imports and remove the lucide-react import
if it is unused.
🤖 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.

Outside diff comments:
In `@apps/web/src/components/layouts/Header.tsx`:
- Around line 96-114: Remove the later duplicate HeaderProps interface and
Header function declaration, retaining the existing public interface and single
Header implementation in the file. Verify the retained implementation’s imports
and remove the lucide-react import if it is unused.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8a539b84-e8d8-482a-8e0d-3448e701f799

📥 Commits

Reviewing files that changed from the base of the PR and between 311027d and 4eb9d45.

📒 Files selected for processing (1)
  • apps/web/src/components/layouts/Header.tsx

@Joyyyb Joyyyb closed this Aug 4, 2026
@Joyyyb
Joyyyb deleted the fix/header-connected-badge-null-address branch August 4, 2026 00:22
@Joyyyb
Joyyyb restored the fix/header-connected-badge-null-address branch August 4, 2026 00:22
@Joyyyb Joyyyb reopened this Aug 4, 2026
@Joyyyb

Joyyyb commented Aug 12, 2026

Copy link
Copy Markdown
Author

Hello Maintainer, issue 389 hasn't been merged. I thought to call your attention to it. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

web(Header): show loading state until wallet public key resolves

2 participants