Skip to content

Fire Mode: BrowserModeStateHolder consolidation#9224

Open
0nko wants to merge 14 commits into
feature/ondrej/fire-mode-pixelsfrom
feature/ondrej/fire-mode-state-holder-refactor
Open

Fire Mode: BrowserModeStateHolder consolidation#9224
0nko wants to merge 14 commits into
feature/ondrej/fire-mode-pixelsfrom
feature/ondrej/fire-mode-state-holder-refactor

Conversation

@0nko

@0nko 0nko commented Jul 17, 2026

Copy link
Copy Markdown
Member

Task/Issue URL: https://app.asana.com/1/137249556945/project/1207418217763355/task/1216655275619568?focus=true
API Proposal: https://app.asana.com/1/137249556945/project/1207418217763355/task/1216655924244593?focus=true

Description

Mostly mechanical rewiring of DI dependencies. The PR removes all ambient reads of the mutable global BrowserModeStateHolder.currentMode so mode-dependent
components act on a frozen, correct BrowserMode:

  • Fixes a latent bug: clearing chats from the Duck.ai history screen in Fire mode dispatched the clear
    labeled as REGULAR (it worked only because the deleter chain also sampled the global mode).
    The ViewModel now passes the activity's frozen mode.
  • Dissolves ModeAwareDuckAiChatStore: the unqualified DuckAiChatStore is now an ActivityScope
    resolution of the frozen mode; the deleter chain is explicitly @RegularMode (Fire deletions are
    routed directly to the Fire store by the data-clearing plugin).
  • Converts the remaining AppScope readers to explicit mode parameters or frozen-mode injection:
    ShowOnAppLaunchOptionHandler, DuckChatSyncRepository.record*, the chat-sync promo plugin path,
    AutoComplete creation, the autofill import promo (mode bound at the tab-wiring seam), and re-scopes
    RealEscapeHatchTargetResolver to ActivityScope.
  • Adds a DenyListedApi lint entry banning currentMode reads (detector extended to see Kotlin
    property getters). The seven irreducible readers (freeze/launch boundaries, mode-switch
    orchestrators, mode-transition observers) carry @Suppress + a one-line justification each.

No UI or user-facing behavior changes besides the Fire-mode history clear fix.

Deny-list exceptions

Every remaining BrowserModeStateHolder.currentMode read carries @Suppress("DenyListedApi") and exists for one of three reasons — it mints the frozen value, it orchestrates the switch, or it consumes mode transitions, none of which a frozen per-activity value can replace:

  • BrowserModeActivityModule.provideActivityBrowserMode — the freeze boundary itself: the single place that samples the holder to mint every activity's frozen BrowserMode, so it cannot inject the value it produces.
  • FirstScreenHandlerImpl (two reads) — runs on process-level app-open lifecycle callbacks, where no activity component (and therefore no frozen mode) exists yet.
  • BrowserViewModel.currentMode — mode-switch orchestrator: it writes the mode via switchTo() and must observe the live value it mutates to drive the switch UX.
  • TabSwitcherViewModel.currentMode — hosts the Regular/Fire toggle: the same orchestrator role, writing via switchTo() and reacting to the live value.
  • ExternalIntentProcessingStateImpl — cross-activity pending-intent state that must react to mode transitions and survive the activity recreation a switch causes, which a per-activity snapshot cannot represent.
  • FireTabsPromoModeObserver — fires once on the first entry into Fire mode, which requires observing the stream of transitions rather than a snapshot.
  • RealNativeInputStateStore — AppScope tab-state registry that must outlive activity recreation and follow the live mode so native-input surfaces always read the active mode's selected tab.

Steps to test this PR

Fire-mode chat history clear

  • In Fire mode, create a couple of Duck.ai chats; also have some Regular-mode chats
  • Open Duck.ai chat history from the Fire native input / Duck.ai menu
  • Verify it lists the Fire chats
  • Select and clear one chat, then clear all
  • Verify that Fire chats are deleted, Regular chats remain intact
  • Repeat the clear in Regular mode
  • Verify that Regular chats deleted, sync of deletions still triggers

Two-mode smoke of rewired surfaces (Optional, no behavior change expected)

  • Chat suggestions in the input screen show the current mode's chats (both modes)
  • Autocomplete "Switch to tab" suggestions list the current mode's tabs (both modes)
  • Chat-sync promo can appear in Regular chat tab; never appears in Fire
  • Google-passwords import promo never appears in Fire mode
  • App launch honors Show-on-app-launch setting; warm relaunch while in Fire mode still lands in Fire

Note

Medium Risk
Wide DI rewiring across browser, autofill, and Duck.ai with a real Fire-mode chat-clear fix; incorrect mode threading could mis-route tabs, sync, or promos, though tests were updated throughout.

Overview
Replaces most ambient reads of BrowserModeStateHolder.currentMode with an activity-frozen BrowserMode (minted once per activity) or an explicit mode argument passed from callers. Mode-dependent autocomplete, autofill, app-launch handling, Duck.ai sync bookkeeping, and chat history now key off that frozen value instead of sampling the global holder mid-flow.

Autocomplete & searchAutoCompleteFactory.create takes browserMode; DefaultAutoComplete moves to ActivityScope. Switch-to-tab suggestions use the injected mode directly (no flatMapLatest on the holder).

AutofillBrowserAutofill / configurator APIs thread browserMode from the tab through JS setup and runtime config. In-browser Google password import promo uses the passed mode (no import promos in Fire).

App launchShowOnAppLaunchOptionHandler receives currentMode from FirstScreenHandler at process-launch boundaries instead of reading the holder inside the handler.

Duck.ai — Chat history clear uses the activity’s frozen mode (fixes Fire history clears being labeled REGULAR). Sync record* APIs take mode and skip non-syncable modes. Deleter/store paths use @RegularMode where Fire clears bypass the Regular chain. Several readers (chat history repo, suggestions) move to ActivityScope.

Lint — Remaining justified currentMode reads are @Suppress("DenyListedApi") with short comments (freeze boundary, mode-switch orchestrators, transition observers, launch boundaries).

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

0nko and others added 13 commits July 17, 2026 10:28
…instead of hard-coded REGULAR

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the frozen browser mode

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d of sampling the global holder

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…icitly instead of sampling the global holder

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sync promo stops sampling the global holder

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ampling the global holder

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…en browser mode

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t allowlist of boundary and mode-transition readers

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ling the global holder in the import promo

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@0nko 0nko mentioned this pull request Jul 17, 2026

0nko commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

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

@0nko
0nko requested a review from CDRussell July 17, 2026 11:34
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