Skip to content

Hatch: only show after idle return#8336

Merged
malmstein merged 9 commits into
developfrom
feature/david/hatch_only_after_idle
Apr 24, 2026
Merged

Hatch: only show after idle return#8336
malmstein merged 9 commits into
developfrom
feature/david/hatch_only_after_idle

Conversation

@malmstein
Copy link
Copy Markdown
Contributor

@malmstein malmstein commented Apr 22, 2026

Task/Issue URL: https://app.asana.com/1/137249556945/project/1174433894299346/task/1214181027808336?focus=true

Description

The return-to-page Hatch card on the NTP was previously shown whenever a last-accessed tab existed and the showNTPAfterIdleReturn feature flag was enabled. This meant it appeared even when the user manually opened a new tab, which is incorrect — the Hatch should only appear when the NTP was shown as a result of an idle return.

Changes:

  • Added isAfterIdleReturn StateFlow<Boolean> to the NtpAfterIdleManager API, replacing the internal currentAfterIdle AtomicBoolean
  • NewTabReturnHatchViewModel now combines flowLastAccessedTab with isAfterIdleReturn to reactively show/hide the Hatch
  • Added :new-tab-page-api dependency to :browser-ui module

Steps to test this PR

Hatch appears after idle return

  • Enable the showNTPAfterIdleReturn feature flag
  • Set idle threshold to "Always" (0 seconds)
  • Navigate to any website
  • Background the app, then return
  • Verify the NTP is shown with the Hatch card (showing the previous page)
  • Tap the Hatch card and verify it returns to the previous page

Hatch does not appear on user-initiated NTP

  • With the feature flag enabled, open a new tab manually (tap the + button or similar)
  • Verify the Hatch card is NOT shown on the new tab page

NTP shown pixels fire correctly after idle return

  • Enable the feature flag and set idle threshold to "Always"
  • Navigate to a website, background the app, then return
  • Verify m_ntp_after_idle_ntp_shown_after_idle pixel fires
  • Open a new tab manually from the menu
  • Verify m_ntp_after_idle_ntp_shown_user_initiated pixel fires (not the after_idle variant)

Return-to-page and search pixels classify correctly

  • Trigger an idle return (NTP shown after idle)
  • Tap the Hatch card to return to the previous page
  • Verify m_ntp_after_idle_return_to_page_tapped_after_idle pixel fires
  • Trigger another idle return, then use the search bar on the NTP
  • Verify m_ntp_after_idle_bar_used_from_ntp_after_idle pixel fires
  • Open a new tab manually, use the search bar
  • Verify m_ntp_after_idle_bar_used_from_ntp_user_initiated pixel fires

UI changes

No UI changes — the Hatch card appearance is unchanged, only when it appears has changed.


Note

Medium Risk
Changes idle-return classification and lifecycle ordering for NTP state/pixeling, which can affect when the hatch appears and how metrics are recorded across app recreation/backgrounding.

Overview
Ensures the NTP “return-to-page” hatch only appears when the NTP was shown due to an idle return, not when the user manually opens a new tab.

This introduces NtpAfterIdleManager.isAfterIdleReturn (a StateFlow) and updates NtpAfterIdleManagerImpl to track/clear after-idle state safely across onOpen/onClose ordering, with tests updated accordingly.

Idle-return triggering is tightened: FirstScreenHandlerImpl can now synchronously call onIdleReturnTriggered() when reopening directly onto an NTP, ShowOnAppLaunchOptionHandler always marks after-idle when the inactivity option resolves to NTP even if no new tab is added, and BrowserTabViewModel avoids firing NTP-search-submitted pixels during restore flows by requiring url == null.

Reviewed by Cursor Bugbot for commit 55a2251. Bugbot is set up for automated code reviews on this repo. Configure here.

malmstein and others added 3 commits April 22, 2026 16:34
The return-to-page Hatch card on the NTP now only appears when
the NTP was shown as a result of an idle return, not when the
user opens a new tab themselves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the synchronous isAfterIdleReturn() getter with a
StateFlow so the Hatch ViewModel reactively hides when
onNtpShown() resets the idle state. This fixes a race
condition where creating a new tab would show the Hatch
because the ViewModel evaluated before onNtpShown ran.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The StateFlow _isAfterIdleReturn already tracks the same
state. Use its .value for pixel classification instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@joshliebe joshliebe left a comment

Choose a reason for hiding this comment

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

I tested this and m_ntp_after_idle_ntp_shown_user_initiated is fired every time the app is launched on new tab.

Is this expected behavior?

Copy link
Copy Markdown
Contributor

@joshliebe joshliebe left a comment

Choose a reason for hiding this comment

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

@malmstein Will leave it up to you to decide if #8336 (review) is correct or not.

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

malmstein and others added 2 commits April 24, 2026 14:06
…h_only_after_idle

# Conflicts:
#	app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/FirstScreenHandler.kt
#	app/src/main/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandler.kt
#	app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/FirstScreenHandlerImplTest.kt
#	app/src/test/java/com/duckduckgo/app/generalsettings/showonapplaunch/ShowOnAppLaunchOptionHandlerImplTest.kt
Only fire ntpAfterIdleManager.onIdleReturnTriggered() synchronously
from FirstScreenHandlerImpl.onOpen when the currently selected tab is
already an NTP; otherwise let the ShowOnAppLaunchOptionHandler path
fire it when it adds (or preserves) an NTP. Without this gate,
LastOpenedTab/SpecificPage users on a URL tab would leave a stale
pendingAfterIdle flag that a later user-initiated NTP would consume.

Also expand the handler to fire on idle for the NewTabPage branch
even when the user is already on an NTP (no new tab added), so the
already-shown NTP is correctly classified as after-idle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
onUserSubmittedQuery is also called from onViewReady and
restoreWebViewState to replay the previously-saved URL of a tab. At
that moment globalLayoutState is still Browser(isNewTabState = true)
from ViewModel init, so the NTP search-submitted pixel would fire
even though the user never typed into the omnibar. Gate the
notification on url == null so restoration paths (where site was
already built by loadData) are excluded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d039c11. Configure here.

@malmstein malmstein merged commit acb4639 into develop Apr 24, 2026
11 checks passed
@malmstein malmstein deleted the feature/david/hatch_only_after_idle branch April 24, 2026 13:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants