Skip to content

[PM-37255] feat: Integrate fill-assist targeting rules into autofill parser#7066

Open
aj-rosado wants to merge 33 commits into
mainfrom
PM-37256/apply-fill-assist-rules
Open

[PM-37255] feat: Integrate fill-assist targeting rules into autofill parser#7066
aj-rosado wants to merge 33 commits into
mainfrom
PM-37256/apply-fill-assist-rules

Conversation

@aj-rosado

Copy link
Copy Markdown
Contributor

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-37255

📔 Objective

Wires the fill-assist targeting-rules pipeline end-to-end into the Android autofill framework.

When the FillAssistTargetingRules feature flag is enabled and rules are available for the current host, the autofill parser replaces heuristic field detection with site-specific CSS-selector-based matching. Unmatched nodes are excluded entirely — there is no heuristic fallback when rules are active.

What's included:

  • Network layerFillAssistApi, FillAssistService, FillAssistManifestJson, FillAssistFormsJson: fetches a versioned manifest and forms JSON from the fill-assist CDN endpoint.
  • Data layerFillAssistManagerImpl, FillAssistDiskSource: parses CSS selectors into FillAssistRules (tag, id, name, type, role constraints per field), caches rules on disk, syncs on server-config change with a 6-hour re-fetch throttle.
  • CSS selector parser — handles >>> Shadow DOM notation, space-separated CSS descendant selectors (split on whitespace outside […] attribute brackets to preserve attribute values that contain spaces), #id shorthand, and [attr='value'] / [attr="value"] attribute selectors.
  • IntegrationAutofillParserImpl looks up host rules for the focused view's URI and calls AssistStructure.buildFillAssistViews to replace the heuristic view list when rules match.
  • View-node matchingFillAssistViewNodeExtensions.traverseForFillAssist traverses the AssistStructure tree; HtmlInfoExtensions.matchesSelectorClause does the per-node attribute comparison (kept in HtmlInfoExtensions alongside hints() since HtmlInfo.attributes uses android.util.Pair and is untestable in unit tests).
  • TestsFillAssistManagerTest, FillAssistServiceTest, FillAssistViewNodeExtensionsTest (all previously commented-out tests now passing via mockkStatic(HtmlInfo::matchesSelectorClause)), and AutofillParserTests updated for the new constructor parameters.

@aj-rosado aj-rosado added the ai-review-vnext Request a Claude code review using the vNext workflow label Jun 16, 2026
@github-actions github-actions Bot added the app:password-manager Bitwarden Password Manager app context label Jun 16, 2026
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the end-to-end integration of fill-assist targeting rules into AutofillParserImpl, including the new toEffectiveViews gating logic, buildFillAssistViews view-node traversal, matchesSelectorClause attribute matching, and the descendant-selector split in FillAssistManagerImpl. The change is well-structured, feature-flag gated, and backed by thorough unit tests covering both the fill-assist and heuristic-fallback paths.

Code Review Details

No new findings at or above the reporting threshold.

Notes on previously raised threads (verified during this pass):

  • The prior ⚠️ IMPORTANT finding — matchesSelectorClause using a Map keyed on clause values that silently dropped constraints when values collided — is resolved by commit 17a85f62c, which replaces the mapOf(...).all {} with explicit &&-chained matchesAttr checks. Verified in HtmlInfoExtensions.kt:108-122.
  • The prior ❌ CRITICAL (missing FillAssistManager import) and import-ordering SUGGESTED findings are resolved; the import is present in VaultSyncManagerImpl.kt and VaultManagerModule.kt.
  • The DEBT/hot-path re-deserialization and cachedRules memory-barrier threads were addressed by the author (reverted / moved out of scope) and are outside this PR's diff.
  • Remaining open threads are stylistic nits already raised by the human reviewer (magic-string consts, mapOf refactor, formatting) or deferred to follow-up PR 7144 by author agreement.

Verified behavior:

  • toEffectiveViews correctly returns heuristic views when the flag is off, the URI is an androidapp:// scheme, the host has no rules, or rules do not cover the focused partition type.
  • DESCENDANT_SEPARATOR_REGEX (\s+(?![^\[]*])) correctly splits descendant selectors while preserving whitespace inside [attr='...'] attribute values.
  • When fill-assist rules cover the partition but no node matches, the parser returns Unfillable with no heuristic fallback, matching the documented intent.

@aj-rosado aj-rosado changed the title Add fill assist logic to Autofill [PM-37255] feat: Integrate fill-assist targeting rules into autofill parser Jun 17, 2026
@github-actions github-actions Bot added the t:feature Change Type - Feature Development label Jun 25, 2026
@aj-rosado aj-rosado marked this pull request as ready for review June 30, 2026 10:04

// Get inline information if available
val isInlineAutofillEnabled = settingsRepository.isInlineAutofillEnabled
Timber.d("Autofill request isInlineEnabled=$isInlineAutofillEnabled -- ${fillRequest?.id}")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why are we removing this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have added a TON of debug logs. When removing probably removed this one with the extra ones by mistake. Will re-add

* when the feature flag is enabled and the host rules cover the current partition type;
* otherwise returns the heuristic [autofillViews].
*/
private fun resolveEffectiveViews(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What do you think about making this an extension function?

    private fun List<AutofillView>.toEffectiveViews(
        assistStructure: AssistStructure,
        uri: String?,
        focusedView: AutofillView,
        urlBarWebsite: String?,
    ): List<AutofillView> {

?.takeUnless { it.startsWith("androidapp://") }
?.toUri()
?.host
?.takeIf { featureFlagManager.getFeatureFlag(FlagKey.FillAssistTargetingRules) }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we just return early instead of putting this check in the chain?

        if (!featureFlagManager.getFeatureFlag(FlagKey.FillAssistTargetingRules)) {
            return autofillViews
        }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

https://github.com/bitwarden/android/pull/7075/changes#diff-e1c5c53907c2162defc188d108bdeb90c4792990fbd3ea388b62e75731c2da6cL215

Next PR in chain will remove that. Are you ok if we keep it here for now to avoid more conflicts when merging?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why is it being removed in that PR?

@aj-rosado aj-rosado Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved the validation to outside this method

val effectiveViews = if (isFillAssistEnabled) {
            resolveEffectiveViews(
                assistStructure = assistStructure,
                autofillViews = autofillViews,
                uri = uri,
                focusedView = focusedView,
                urlBarWebsite = urlBarWebsite,
            )
        } else {
            autofillViews
        }

val urlBarWebsite = traversalDataList
.flatMap { it.urlBarWebsites }
.firstOrNull()
val autofillViews = traversalDataList.toAutofillViews(urlBarWebsite = urlBarWebsite)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The new buildFillAssistViews is awfully similar to the existing logic for parsing the assist structure. It's hard for me to tell if it is possible but can we optimize this to parse the structure only once?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is possible but as It adds complexity to the traverse, not sure if it a real improvement.
I have created a draft PR with a possible solution for this.
#7144

@codecov

codecov Bot commented Jul 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.33628% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.16%. Comparing base (fe2e7c0) to head (17a85f6).

Files with missing lines Patch % Lines
...bitwarden/data/autofill/util/HtmlInfoExtensions.kt 0.00% 12 Missing ⚠️
...twarden/data/autofill/parser/AutofillParserImpl.kt 82.69% 2 Missing and 7 partials ⚠️
...bitwarden/data/autofill/util/ViewNodeExtensions.kt 0.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7066      +/-   ##
==========================================
- Coverage   86.76%   86.16%   -0.61%     
==========================================
  Files         868      913      +45     
  Lines       63975    65677    +1702     
  Branches     9585     9630      +45     
==========================================
+ Hits        55511    56591    +1080     
- Misses       5023     5637     +614     
- Partials     3441     3449       +8     
Flag Coverage Δ
app-data 17.42% <74.33%> (-0.18%) ⬇️
app-ui-auth-tools 18.79% <0.00%> (-0.01%) ⬇️
app-ui-platform 16.49% <0.00%> (+0.01%) ⬆️
app-ui-vault 28.01% <0.00%> (+0.51%) ⬆️
authenticator 6.12% <0.00%> (-0.06%) ⬇️
lib-core-network-bridge 4.04% <0.00%> (-0.04%) ⬇️
lib-data-ui 1.14% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

?.host
?.takeIf { featureFlagManager.getFeatureFlag(FlagKey.FillAssistTargetingRules) }
?.let { host ->
fillAssistManager.getFillAssistRules()?.hostRules?.get(host.removePrefix("www."))

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.

🧹 DEBT: getFillAssistRules() is invoked on the autofill parse hot path (once per parse() — i.e. per field focus / fill request). Its implementation re-reads the SharedPreferences string and fully re-deserializes the entire rules blob every call:

// FillAssistDiskSourceImpl
override fun getFillAssistRules(serverUrl: String): FillAssistRules? =
    getString(FILL_ASSIST_RULES_KEY.appendIdentifier(serverUrl))
        ?.let { json.decodeFromStringOrNull(it) }

Since the cached blob can contain rules for many hosts, this JSON parse repeats on every autofill interaction with no in-memory memoization. Consider caching the deserialized FillAssistRules in memory (invalidated on sync) so the parse path avoids repeated full deserialization. Gated behind the feature flag, so not urgent — but worth addressing before broad rollout.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude updated

@claude claude Bot Jul 6, 2026

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.

Claude finished @aj-rosado's task in 1m 20s —— View job


Verifying the caching update

  • Reviewed the cache fill assist rules commit (1d3381f)
  • Confirmed the hot-path re-deserialization is resolved
  • Checked test coverage

Resolved. The DEBT concern is addressed. getFillAssistRules() now memoizes the deserialized FillAssistRules in the cachedRules field and short-circuits before touching disk (FillAssistManagerImpl.kt:146), so the full JSON blob is no longer re-parsed on every parse() call. Invalidation is wired correctly:

  • reset on server-config change (init block, :78)
  • reset after a sync stores new rules (:137)

Test coverage is solid — the three new cases cover the cache hit (disk read exactly = 1), re-read after server-config change, and returning the latest rules after a sync.

One carry-over note: this newly-added cachedRules var is written from three contexts (init/unconfined, sync/io, and getFillAssistRules on the binder thread) without @Volatile. That's the same publication concern already tracked in the sibling ♻️ DEBT thread on this field — marking @Volatile there would also cover this cache. No action needed here beyond that existing thread.

} answers {
mutableFillAssistFlagFlow.value
}
every { getFeatureFlagFlow(FlagKey.ManageDevices) } returns mutableFillAssistFlagFlow

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.

📝 SUGGESTED: This stub wires the fill-assist flow to FlagKey.ManageDevices, but AutofillParserImpl only ever calls getFeatureFlag(FlagKey.FillAssistTargetingRules) — never getFeatureFlagFlow. This looks like a copy-paste leftover; it's dead setup and misleadingly ties the test's flag flow to an unrelated flag. Recommend removing this line.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude updated

@claude claude Bot Jul 6, 2026

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.

Claude finished @aj-rosado's task in 58s —— View job


Verified — FlagKey.ManageDevices copy-paste leftover resolved ✅

Confirmed the update. The stub now wires to the correct flag:

private val mutableFillAssistFlagFlow = MutableStateFlow(false)
private val featureFlagManager: FeatureFlagManager = mockk {
    every {
        getFeatureFlag(FlagKey.FillAssistTargetingRules)
    } answers {
        mutableFillAssistFlagFlow.value
    }
    every {
        getFeatureFlagFlow(FlagKey.FillAssistTargetingRules)
    } returns mutableFillAssistFlagFlow
}

The misleading tie to the unrelated FlagKey.ManageDevices is gone, and the MutableStateFlow now cleanly backs the flag value that the tests toggle (mutableFillAssistFlagFlow.value = true at lines 1004/1064/1124/1197). Original concern resolved.

One residual nit (non-blocking): AutofillParserImpl only ever calls getFeatureFlag(...) — never getFeatureFlagFlow(...) (confirmed at AutofillParserImpl.kt:220). So the getFeatureFlagFlow stub (lines 94–95) is still unexercised setup and could be dropped. Purely cosmetic — no action required.

import com.bitwarden.vault.FolderView
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
import com.x8bit.bitwarden.data.autofill.manager.FillAssistManager

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.

📝 SUGGESTED: Import out of order — autofill.manager.FillAssistManager is placed between the two auth.manager imports (auth sorts before autofill). Run IDE optimize-imports so it lands after UserStateManager. Same misplacement exists in VaultManagerModule.kt. Minor (detekt ImportOrdering is disabled, so CI won't catch it) but deviates from the codebase's sorted-import convention.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude updated

@claude claude Bot Jul 6, 2026

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.

Claude finished @aj-rosado's task in 2m 17s —— View job


Import fix review

  • Read VaultSyncManagerImpl.kt imports
  • Read VaultManagerModule.kt imports
  • Assess whether the update was correct
  • Fix the regression and push

⚠️ The fix in f677f80 (Fixing fillAssistManager import) removed the FillAssistManager import entirely from VaultSyncManagerImpl.kt instead of reordering it. But FillAssistManager is still referenced at VaultSyncManagerImpl.kt:83 (constructor param) and :346 (fillAssistManager.syncIfNecessary()), and it lives in a different package (com.x8bit.bitwarden.data.autofill.manager) — so this would have broken compilation.

I pushed a fix (2219e6f) that:

  1. VaultSyncManagerImpl.kt — re-added the import in its correctly sorted position (after auth.repository.util.userSwitchingChangesFlow, before platform.datasource.disk.SettingsDiskSource, since autofill sorts after auth and before platform).
  2. VaultManagerModule.kt — this file still had the import misplaced between the vault.manager.* imports (line 47). Moved it up to after auth.manager.UserStateManager, matching the sorted convention.

Both imports now land where IDE optimize-imports would put them.

💡 Recommend re-running a local build (./gradlew app:assembleStandardDebug or the compile task) before merging to confirm — the removed import would have surfaced as a hard compile error, so it's worth a green build check.

Comment on lines +61 to +72
private val LOGIN_FILL_ASSIST_CATEGORIES: List<String> = listOf(
"account-login",
"account-creation",
"account-update",
)

/**
* A list of categories from Fill Assist that are used for [AutofillView.Card]
*/
private val CARD_FILL_ASSIST_CATEGORIES: List<String> = listOf(
"payment-card",
)

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.

QUESTION: These hardcoded category allowlists are the gate that decides whether fill-assist replaces heuristics for a given partition. Any login-related category the CDN emits that isn't listed here will silently fall back to heuristics. Notably, FillAssistViewNodeExtensionsTest uses the category "account-registration" (with a newPassword field), which is not in LOGIN_FILL_ASSIST_CATEGORIES — so a real registration form served with that category would never trigger fill-assist even though the field mapping supports it.

Is the complete set of server categories confirmed to be exactly account-login/account-creation/account-update (+ payment-card)? If new categories can appear, consider deriving the partition mapping from the field keys present in the rule rather than a fixed category allowlist, to avoid silent gaps as the schema evolves.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If a new category appears we will need to manually add it. As this falls back to heuristics is not a problem.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude answered

@claude claude Bot Jul 6, 2026

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.

Claude finished @aj-rosado's task in 42s —— View job


Verified — hardcoded category allowlist behavior confirmed ✅

Confirmed the author's answer. The concern raised was that any login-related category the CDN emits that isn't in LOGIN_FILL_ASSIST_CATEGORIES / CARD_FILL_ASSIST_CATEGORIES would silently fall back to heuristics.

Re-reading the gating logic at AutofillParserImpl.kt:226-241, the answer holds:

  • When coversCurrentPartition is false (i.e. an unlisted category), toEffectiveViews returns this — the heuristic autofill views. That's the existing, working production path, so an unrecognized category degrades gracefully rather than breaking autofill. Not a correctness issue.
  • Adding a new category is a one-line change to the relevant allowlist, which is easy to catch and extend when the schema evolves.

On the specific "account-registration" note from the original comment: I checked FillAssistViewNodeExtensionsTest.kt:114 — that category is only exercised against buildFillAssistViews / traverseForFillAssist (the field-mapping/traversal layer), not the toEffectiveViews category gate. So there's no test asserting fill-assist coverage for a category that the gate would reject; the two layers are tested independently and there's no misleading assertion. My original example conflated the two layers.

Resolving as answered — the fall-back-to-heuristics design is a deliberate and safe choice. 👍

@aj-rosado aj-rosado requested a review from david-livefront July 6, 2026 14:13

// In-memory cache to avoid re-deserializing the full rules blob on every autofill parse.
// Populated lazily on first read; updated in-place when a sync stores new rules.
private var cachedRules: FillAssistRules? = null

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.

♻️ DEBT: cachedRules is a plain var written from three concurrency contexts without a memory barrier.

Details and fix

cachedRules is written from:

  • the init block on unconfinedScope (cachedRules = null on server-config change),
  • sync() on ioScope (cachedRules = null after storing new rules),
  • getFillAssistRules() (?.also { cachedRules = it }), which runs on the binder thread that the autofill framework invokes parse() on.

Because the field is neither @Volatile nor guarded by a lock, a cachedRules = null reset performed on one thread may not be visible to the parser thread, so a parse can serve stale rules after a server-config change until the JVM happens to publish the write.

The impact is benign (transient stale rules or an extra disk read, no crash), but a cheap fix restores the intended invalidation semantics:

@Volatile
private var cachedRules: FillAssistRules? = null

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude reverted this change, we are getting the data from sharedPreferences and not disk, this cache is not really necessary

@claude claude Bot Jul 6, 2026

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.

Claude finished @aj-rosado's task in 56s —— View job


Agreed — reverting the cache is the right call ✅

  • Re-read FillAssistManagerImpl.getFillAssistRules() (post-revert)
  • Traced the read path into FillAssistDiskSourceImpl
  • Re-evaluated the original DEBT concern

You're right, and thanks for pushing back — my original DEBT note conflated two different costs.

On disk I/O: getFillAssistRules()FillAssistDiskSourceImpl.getFillAssistRules()getString(...) (FillAssistDiskSourceImpl.kt:35). SharedPreferences loads its backing file into an in-memory map once and serves getString from there — so there's no per-call disk read. My phrasing "re-reads the SharedPreferences string" implied I/O that isn't happening. You're correct on that.

On the residual cost: the only thing that still repeats per call is the json.decodeFromStringOrNull(...) deserialization. But parse() fires per field-focus / fill request — a user-initiated interaction, not a tight loop — so a single JSON parse per autofill interaction is negligible in practice. The in-memory cache was buying a micro-optimization on a low-frequency path.

Net: reverting is the better trade-off. It removes the added complexity and eliminates the @Volatile/publication concern from the sibling ♻️ DEBT thread on cachedRules (that field no longer exists, so that thread is moot too). No action needed here.

If deserialization ever does show up on a profile once this is broadly rolled out, an in-memory cache can be reintroduced then — but it's not warranted now. Resolving as answered. 👍

aj-rosado and others added 4 commits July 6, 2026 16:37
The previous fix removed the FillAssistManager import from
VaultSyncManagerImpl.kt entirely, breaking compilation since the class
is still referenced. Re-add it in its correctly sorted position, and
also correct the misplaced import in VaultManagerModule.kt.

Co-authored-by: aj-rosado <aj-rosado@users.noreply.github.com>
val HtmlInfo?.isInputField: Boolean get() = this?.tag == "input"

/**
* Whether this [HtmlInfo] matches the given [SelectorClause].

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The link here is broken, can we make it [FillAssistRules.SelectorClause]

clause.name == null &&
clause.type == null &&
clause.role == null
fun hasAttr(key: String, value: String) = attrs.any { it.first == key && it.second == value }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The formatter really wants a newline before the inline function

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ping

clause.type == null &&
clause.role == null
fun hasAttr(key: String, value: String) = attrs.any { it.first == key && it.second == value }
return listOf(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can be mapOf

val data = toAutofillViewData(autofillId = id, website = website)
matchingEntries.firstNotNullOfOrNull { (key, _) ->
key.toAutofillViewForFieldKey(
data,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This should either have a name or be 1 line.

    key.toAutofillViewForFieldKey(data)

OR

    key.toAutofillViewForFieldKey(
        data = data,
    )

"cardholderName" -> AutofillView.Card.CardholderName(data = data)
"cardExpirationDate" -> AutofillView.Card.ExpirationDate(data = data)
"cardExpirationMonth" -> AutofillView.Card.ExpirationMonth(data = data, monthValue = null)
"cardExpirationYear" -> AutofillView.Card.ExpirationYear(data = data, yearValue = null)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we make some consts for these magic strings?

clause.id to "id",
clause.name to "name",
clause.type to "type",
clause.role to "role",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we make these consts?

private val authDiskSource: AuthDiskSource,
private val vaultDiskSource: VaultDiskSource,
private val vaultSdkSource: VaultSdkSource,
private val fillAssistManager: FillAssistManager,

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.

CRITICAL: FillAssistManager is referenced but not imported — this will not compile.

Details and fix

The most recent commit (643be12 "Revert 'Restore FillAssistManager import and fix import ordering'") removed the import com.x8bit.bitwarden.data.autofill.manager.FillAssistManager line from this file, but the constructor parameter here (line 83) and the fillAssistManager.syncIfNecessary() call (line 343) still reference the type.

FillAssistManager lives in com.x8bit.bitwarden.data.autofill.manager, a different package from this file's com.x8bit.bitwarden.data.vault.manager, so it cannot resolve without an explicit import — this is an unresolved reference / compile error.

Restore the import (in alphabetical position, after ...auth.repository.util.userSwitchingChangesFlow):

import com.x8bit.bitwarden.data.autofill.manager.FillAssistManager

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@claude This is now fixed

@claude claude Bot Jul 7, 2026

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.

Claude finished @david-livefront's task in 31s —— View job


Verified — FillAssistManager import restored ✅

  • Read VaultSyncManagerImpl.kt imports
  • Read VaultManagerModule.kt imports
  • Confirmed both are correctly sorted

Confirmed — the CRITICAL compile issue is resolved.

VaultSyncManagerImpl.kt:23import com.x8bit.bitwarden.data.autofill.manager.FillAssistManager is present and correctly sorted (after auth.repository.util.userSwitchingChangesFlow, before platform.datasource.disk.SettingsDiskSource). The constructor param (:84) now resolves.

VaultManagerModule.kt:21 — same import present and correctly sorted (after auth.manager.UserStateManager, before platform.datasource.disk.SettingsDiskSource), matching the sorted convention flagged in the sibling thread.

Both land where IDE optimize-imports would put them. The unresolved-reference / compile error is gone. 👍

import com.x8bit.bitwarden.data.vault.manager.VaultLockManagerImpl
import com.x8bit.bitwarden.data.vault.manager.VaultMigrationManager
import com.x8bit.bitwarden.data.vault.manager.VaultMigrationManagerImpl
import com.x8bit.bitwarden.data.autofill.manager.FillAssistManager

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.

🎨 SUGGESTED: Import is out of alphabetical order — the same 643be12 revert moved it here, between the vault.manager.* imports.

Details

com.x8bit.bitwarden.data.autofill.manager.FillAssistManager belongs with the other data.auth/data.autofill imports near the top of the block (before data.platform.*), not between VaultMigrationManagerImpl and VaultSyncManager. This compiles fine (detekt ImportOrdering is disabled), but it's inconsistent with the rest of the file's ordering.

Comment on lines +117 to +123
return mapOf(
clause.id to HTML_ATTR_ID,
clause.name to HTML_ATTR_NAME,
clause.type to HTML_ATTR_TYPE,
clause.role to HTML_ATTR_ROLE,
)
.all { (value, key) -> value == null || hasAttr(key, value) }

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.

⚠️ IMPORTANT: Building a Map keyed on the clause attribute values silently drops constraints when two values collide.

Details and fix

The map keys here are the clause values (clause.id, clause.name, ...), not the attribute names. When two of these values are equal, the map collapses them and only the last entry survives — so one of the attribute checks is silently skipped.

This is not a contrived case: id and name are frequently identical in real HTML forms (e.g. <input id="email" name="email"> → selector input#email[name='email'] yields clause.id == "email" and clause.name == "email"). The map becomes {"email" -> "name"}, so the id constraint is never verified and a node with a matching name but a different id will incorrectly match.

Because matchesSelectorClause is documented as untestable in unit tests, this cannot be caught by the existing suite. Keying on the attribute name instead avoids the collapse:

fun matchesAttr(value: String?, key: String) = value == null || hasAttr(key, value)
return matchesAttr(clause.id, HTML_ATTR_ID) &&
    matchesAttr(clause.name, HTML_ATTR_NAME) &&
    matchesAttr(clause.type, HTML_ATTR_TYPE) &&
    matchesAttr(clause.role, HTML_ATTR_ROLE)

@aj-rosado aj-rosado requested a review from david-livefront July 7, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review-vnext Request a Claude code review using the vNext workflow app:password-manager Bitwarden Password Manager app context t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants