Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/browser/src/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5803,6 +5803,9 @@
"settingDisabledByPolicy": {
"message": "This setting is disabled by your organization's policy.",
"description": "This hint text is displayed when a user setting is disabled due to an organization policy."
},
"importPopoutDialogDesc": {
Copy link

Choose a reason for hiding this comment

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

๐ŸŽจ Finding 7: Missing i18n key for dialog title

Only the description key was added, but the template reuses the Send dialog's title (sendFilePopoutDialogText). This should have a dedicated import title key.

Suggested addition:

"importPopoutDialogTitle": {
  "message": "Pop out required"
},
"importPopoutDialogDesc": {
  "message": "To import from a file, you need to pop out the extension to a new window."
}

"message": "To import from a file, you need to pop out the extension to a new window."
},
"zipPostalCodeLabel": {
"message": "ZIP / Postal code"
Expand Down
4 changes: 2 additions & 2 deletions apps/browser/src/popup/app-routing.module.ts
Copy link
Member

Choose a reason for hiding this comment

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

question: Why are these needed when we show a dialog on the pages when opened in a popup.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Import

With doNotSaveUrl: true : Navigating to the import view, and then closing the pop-out, and then re-opening the main extension view shows the penultimate view to the import menu, the "Vault Options" view

Without doNotSaveUrl: true : Following the same workflow as above, instead of the "Vault Options" view, the pop-out dialog is instead displayed. Clicking cancel returns to the "Vault Options" view, and clicking "Pop Out" will trigger the pop-out.

Send

With doNotSaveUrl: true : Navigating towards send file view, confirming the pop-out, and then closing the pop-out, re-opening the extension (default not popped out state) shows the main "Send" view

Without doNotSaveUrl: true : The same workflow as above results in the "Send File" view being presented outside of the pop-out context. This allows the situation the PR is intended to avoid: a user can attempt to open a file selection dialog outside of a popped out context and the extensiuon will crash

Since only the Send functionality requires this, why was it added to Import as well?

For consistency, it can be safely removed from the import routing logic if this is preferred.

Copy link
Member

@Hinton Hinton Nov 20, 2025

Choose a reason for hiding this comment

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

I think I understand why you've resorted to adding doNotTrack. Since SendFilePopoutDialogContainerComponent relies on ngOnInit there is a race condition that primarily occurs when navigating directly to the add-send url without first landing on sends. Where config does not have the necessary values to trigger the open condition.

Is this accurate? And if so should we resolve the root cause rather than rely on the race condition?

@Component({
  selector: "send-file-popout-dialog-container",
  templateUrl: "./send-file-popout-dialog-container.component.html",
  imports: [JslibModule, CommonModule],
})
export class SendFilePopoutDialogContainerComponent {
  private readonly dialogService = inject(DialogService);
  private readonly filePopoutUtilsService = inject(FilePopoutUtilsService);

  readonly config = input.required<SendFormConfig>();

  /**
   * Tracks if the dialog has already been opened. This prevents multiple dialogs from opening if config is updated.
   */
  private readonly dialogOpened = signal(false);

  constructor() {
    effect(() => {
      if (this.dialogOpened()) {
        return;
      }

      if (
        this.config().sendType === SendType.File &&
        this.config().mode === "add" &&
        this.filePopoutUtilsService.showFilePopoutMessage(window)
      ) {
        this.dialogService.open(SendFilePopoutDialogComponent, {
          positionStrategy: new CenterPositionStrategy(),
        });
        this.dialogOpened.set(true);
      }
    });
  }
}

Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ const routes: Routes = [
path: "import",
component: ImportBrowserV2Component,
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties,
},
{
path: "export",
Expand Down Expand Up @@ -324,7 +324,7 @@ const routes: Routes = [
path: "add-send",
component: SendAddEditV2Component,
canActivate: [authGuard],
data: { elevation: 1 } satisfies RouteDataProperties,
data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties,
Copy link
Member

Choose a reason for hiding this comment

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

question: Why are we not saving the url here?

Copy link
Contributor Author

@harr1424 harr1424 Nov 7, 2025

Choose a reason for hiding this comment

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

Copying from Claude's comment (below) which correctly notes this logic wasn't applied to the import path:

Without this flag, the import page URL is saved in popup history
If a user navigates away and the popup restores this URL, they could still access the file selector outside the popout
This defeats the purpose of the Firefox crash fix

Copy link

Choose a reason for hiding this comment

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

๐Ÿ’ญ Finding 5: Missing route configuration for import path

The add-send route has doNotSaveUrl: true to prevent saving the URL when the popout dialog might be shown. However, there's no corresponding route configuration change for the import page.

Questions:

  1. What is the route path for ImportBrowserV2Component? (Not visible in the diff)
  2. Does that route also need doNotSaveUrl: true for consistency?
  3. Without doNotSaveUrl, users who close the popout dialog will have the import page saved in history, which could lead to the file selector issue recurring

This addresses the reviewer's question about why we're not saving the URL here.

Copy link

Choose a reason for hiding this comment

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

โŒ Finding 8: Import route missing doNotSaveUrl: true flag

The import route (line 246-249) should also have data: { elevation: 1, doNotSaveUrl: true } for consistency with the Send route change.

Why this matters:

  • Without this flag, the import page URL is saved in popup history
  • If a user navigates away and the popup restores this URL, they could still access the file selector outside the popout
  • This defeats the purpose of the Firefox crash fix

Required change:

{
  path: "import",
  component: ImportBrowserV2Component,
  canActivate: [authGuard],
  data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties,
}

},
{
path: "edit-send",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { Router } from "@angular/router";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components";
Expand All @@ -14,13 +15,18 @@ import BrowserPopupUtils from "../../../../platform/browser/browser-popup-utils"
imports: [JslibModule, CommonModule, DialogModule, ButtonModule, TypographyModule],
})
export class SendFilePopoutDialogComponent {
constructor(private dialogService: DialogService) {}
constructor(
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 4: Use inject() instead of constructor injection

Suggested change
constructor(
private dialogService = inject(DialogService);
private router = inject(Router);

private dialogService: DialogService,
private router: Router,
) {}

Choose a reason for hiding this comment

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

use inject instead of constructor

async popOutWindow() {
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 5: Missing return type annotation

Suggested change
async popOutWindow() {
async popOutWindow(): Promise<void> {

await BrowserPopupUtils.openCurrentPagePopout(window);
}

close() {
async close() {

Choose a reason for hiding this comment

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

return type

Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 5: Missing return type annotation

Suggested change
async close() {
async close(): Promise<void> {

this.dialogService.closeAll();
// the current view exposes a file selector, ensure the view is popped to avoid using it outside of a popout
await this.router.navigate(["/tabs/send"]);
Copy link

Choose a reason for hiding this comment

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

โ™ป๏ธ Finding 3: Inconsistent navigation patterns create technical debt

The Send dialog uses Router.navigate() to an absolute path while the Import dialog uses popupRouterCacheService.back(). This inconsistency creates confusion and maintenance burden.

Issue: Both components solve the same problem (prevent file selector outside popout) but use different navigation strategies.

Recommendation: Standardize on one approach:

  • Option A: Both use explicit navigation to a known route
  • Option B: Both use the router cache service (requires refactoring Send)
  • Option C: Extract shared navigation logic to FilePopoutUtilsService

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
>
</tools-import>

<import-file-popout-dialog-container></import-file-popout-dialog-container>
Copy link
Member

Choose a reason for hiding this comment

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

question: Why is this a separate component rather than just living inside the import-browser component? It seems quite weird to have a child component popup a dialog that redirects the parent page.

Copy link

Choose a reason for hiding this comment

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

๐Ÿ’ญ Finding 4: Component placement and responsibilities need clarification

The <import-file-popout-dialog-container> is embedded in the template but has no visual representation (empty template). This creates unclear component boundaries.

Questions for consideration:

  1. Why is this a separate component rather than logic in the parent ImportBrowserV2Component?
  2. The container component opens a dialog that navigates the parent - this violates typical component encapsulation
  3. Should this logic live in a route guard instead, since it's checking conditions on route entry?

Alternative approaches:

  • Move the dialog-opening logic to ImportBrowserV2Component.ngOnInit()
  • Or use a route guard that opens the dialog before route activation
  • Or keep as child component but have it emit events rather than directly navigating

This addresses the reviewer's question about architecture.

Copy link

Choose a reason for hiding this comment

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

๐Ÿ’ญ Finding 2: Component architecture violates encapsulation principles

This container component has no visual output (empty template) but opens a dialog that navigates the parent page, creating unclear boundaries.

Questions:

  1. Why is this a separate component rather than logic in ImportBrowserV2Component.ngOnInit()?
  2. Should this be a route guard instead since it checks conditions on route entry?

Alternative: Move dialog logic directly to parent component or implement as canActivate guard.

Choose a reason for hiding this comment

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

dont need closing tag

Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 6: Unnecessary closing tag

Self-closing components don't need closing tags in Angular.

Suggested change
<import-file-popout-dialog-container></import-file-popout-dialog-container>
<import-file-popout-dialog-container />


<popup-footer slot="footer">
<button
[disabled]="disabled"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { PopupFooterComponent } from "../../../../platform/popup/layout/popup-fo
import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-header.component";
import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component";

import { ImportFilePopoutDialogContainerComponent } from "./import-file-popout-dialog-container.component";

// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
Expand All @@ -35,6 +37,7 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page
PopupFooterComponent,
PopupHeaderComponent,
PopOutComponent,
ImportFilePopoutDialogContainerComponent,
],
providers: [
...ImporterProviders,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CommonModule } from "@angular/common";
import { Component, OnInit, ChangeDetectionStrategy, OnDestroy } from "@angular/core";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { DialogRef, DialogService } from "@bitwarden/components";

import { FilePopoutUtilsService } from "../../services/file-popout-utils.service";

import { ImportFilePopoutDialogComponent } from "./import-file-popout-dialog.component";

@Component({
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 3: Component should be standalone

New Angular components should include standalone: true per project modernization efforts.

Suggested change
@Component({
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,

Copy link
Member

Choose a reason for hiding this comment

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

@theMickster this seems wrong. We actually omit standalone and only want to flag this if the component includes standalone: false

Copy link
Contributor

Choose a reason for hiding this comment

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

@claude: Why did you make this finding? Did you read a contributing document, a README.md, a CLAUDE.md, or similar that brought you to this conclusion?

Copy link
Contributor

Choose a reason for hiding this comment

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

Sad panda ๐Ÿผ
Seems like the respond workflow that we tried to create isn't working. I'll take a look, but I might need some help going into the weeds as to why it's not working @mandreko-bitwarden

Copy link
Contributor

Choose a reason for hiding this comment

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

@claude: Why did you make this finding? Did you read a contributing document, a README.md, a CLAUDE.md, or similar that brought you to this conclusion?

Copy link

@claude claude bot Nov 19, 2025

Choose a reason for hiding this comment

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

Claude finished @theMickster's task โ€”โ€” View job


Explanation of my finding

I apologize for the confusion in my review comment. After investigating the project documentation and codebase patterns, I can now explain my reasoning and acknowledge the error:

What I found in the documentation:

  • The CLAUDE.md file does not contain any specific guidance about standalone components
  • The CONTRIBUTING.md and README.md also don't mention standalone component patterns

What I found in the codebase:
After examining existing components in the browser app, I can see the actual pattern:

  1. Most new components simply include an imports: [...] array and omit the standalone property entirely (it defaults to true in modern Angular when using imports)
  2. Only explicitly set standalone: false for legacy module-based components (e.g., remove-password.component.ts)
  3. Never explicitly set standalone: true because it's redundant when using the imports array

My mistake:
I incorrectly suggested adding standalone: true based on a general Angular modernization assumption, not from any project-specific documentation. The component in question (import-file-popout-dialog-container.component.ts) already follows the correct pattern by:

  • Including an imports: [JslibModule, CommonModule] array
  • Omitting the standalone property (which makes it standalone by default)

@Hinton's correction was right: We should only flag standalone: false as an issue, not the absence of standalone: true.

My finding was based on a misunderstanding of Angular's default behavior and not from any contributing documentation. Thank you for pushing back on this - it helps improve the accuracy of future reviews.


changeDetection: ChangeDetectionStrategy.OnPush,
selector: "import-file-popout-dialog-container",
template: "",
imports: [JslibModule, CommonModule],
})
export class ImportFilePopoutDialogContainerComponent implements OnInit, OnDestroy {
Copy link

Choose a reason for hiding this comment

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

๐Ÿ’ญ Component architecture question

This container component has an empty template and only opens a dialog that navigates the parent page. This creates unclear component boundaries.

Alternative approaches to consider:

  1. Move dialog logic directly to ImportBrowserV2Component.ngOnInit()
  2. Implement as a route guard (canActivate) that checks popout conditions
  3. Keep current approach but add JSDoc explaining the design choice

The current approach works but is unusual compared to typical Angular patterns.

private dialogRef: DialogRef | null = null;

constructor(
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 4: Use inject() instead of constructor injection

Per Angular modernization patterns, prefer inject():

Suggested change
constructor(
private dialogService = inject(DialogService);
private filePopoutUtilsService = inject(FilePopoutUtilsService);

private dialogService: DialogService,
private filePopoutUtilsService: FilePopoutUtilsService,
) {}

Choose a reason for hiding this comment

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

inject


ngOnInit() {
if (this.filePopoutUtilsService.showFilePopoutMessage(window)) {
this.dialogRef = this.dialogService.open(ImportFilePopoutDialogComponent);
}
}

ngOnDestroy() {
this.dialogRef?.close();
}
}
Copy link
Member

Choose a reason for hiding this comment

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

suggestion: This seems pretty much identical to the import popout dialog. Should they be combined and you just feed it the title/body to use?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, but I am not sure I completely understand:

This seems pretty much identical to the import popout dialog

Which file are you referring to here?

Copy link
Member

Choose a reason for hiding this comment

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

Apologies, SendFilePopoutDialogComponent

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Hinton this definitley seems possible at first, and there is actually a FilePopoutComponent that curiously wasn't being used on the existing Send File workflow.

When I started work on this PR I began to suspect it is because FilePopoutComponent doesn't have any logic controlling cancellation behavior. Maybe this is preferred?

I've used the cancellation logic in ImportFilePopoutDialogComponent and SendFilePopoutDialogComponent to ensure the user is navigated away from a view that exposes a file selector, but since these components are at different view hierarchies, the navigation logic needs to be handled differently (this touches on another review comment):

export class ImportFilePopoutDialogComponent {
...

    // If the user selects "cancel" when presented the dialog, navigate back to the main Send tab
  async close() {
    this.dialogService.closeAll();
    await this.popupRouterCacheService.back();
  }
}
...
  // If the user selects "cancel" when presented the dialog, navigate back to the main Send tab
  async close() {
    this.dialogService.closeAll();
    await this.router.navigate(["/tabs/send"]);
  }
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<bit-simple-dialog dialogSize="default">
<div bitDialogIcon>
<i class="bwi bwi-info-circle bwi-2x tw-text-info" aria-hidden="true"></i>
</div>
<ng-container bitDialogContent>
<div bitTypography="h3">
{{ "sendFilePopoutDialogText" | i18n }}
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 6: Incorrect i18n key reuse creates confusing UX

Line 7 uses "sendFilePopoutDialogText" (Send-related) while line 9 correctly uses "importPopoutDialogDesc" (Import-related). This creates a confusing message that says "Send file" in the title but talks about importing in the description.

Expected behavior: The title should use an import-specific i18n key.

Fix needed:

<div bitTypography="h3">
  {{ "importPopoutDialogTitle" | i18n }}
</div>

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out required"
}

Copy link

Choose a reason for hiding this comment

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

โŒ Finding 1: Incorrect i18n key creates confusing dialog title

This uses sendFilePopoutDialogText which displays "Pop out extension?" with a description about Send. However, line 9 correctly uses importPopoutDialogDesc for the description, creating inconsistent messaging.

Required fix:

Suggested change
{{ "sendFilePopoutDialogText" | i18n }}
{{ "importPopoutDialogTitle" | i18n }}

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out to import"
}

Copy link

Choose a reason for hiding this comment

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

โŒ Finding 1: Wrong i18n key creates confusing dialog title

This uses sendFilePopoutDialogText which displays "Pop out extension?" (a Send-specific message), but line 9 correctly uses importPopoutDialogDesc for the description. This creates inconsistent messaging.

Required fix:

Suggested change
{{ "sendFilePopoutDialogText" | i18n }}
{{ "importPopoutDialogTitle" | i18n }}

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out to import"
}

Copy link

Choose a reason for hiding this comment

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

โŒ Wrong i18n key used

This reuses the Send dialog's title (sendFilePopoutDialogText) instead of an import-specific key. Line 7 correctly uses importPopoutDialogDesc, creating inconsistent messaging.

Fix needed:

Suggested change
{{ "sendFilePopoutDialogText" | i18n }}
{{ "importPopoutDialogTitle" | i18n }}

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out to import"
}

Copy link

Choose a reason for hiding this comment

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

โŒ Wrong i18n key

This uses sendFilePopoutDialogText (Send-specific) but line 7 correctly uses importPopoutDialogDesc (Import-specific). This creates inconsistent messaging.

Required fix:

Suggested change
{{ "sendFilePopoutDialogText" | i18n }}
{{ "importPopoutDialogTitle" | i18n }}

Then add to messages.json:

"importPopoutDialogTitle": {
  "message": "Pop out to import"
}

</div>
<div bitTypography="body1">{{ "importPopoutDialogDesc" | i18n }}</div>
</ng-container>
<ng-container bitDialogFooter>
<button buttonType="primary" bitButton type="button" (click)="popOutWindow()">
{{ "popOut" | i18n }}
<i class="bwi bwi-popout tw-ml-1" aria-hidden="true"></i>
</button>
<button bitButton buttonType="secondary" type="button" (click)="close()">
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-simple-dialog>
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { CommonModule } from "@angular/common";
import { Component, ChangeDetectionStrategy } from "@angular/core";

import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components";

import BrowserPopupUtils from "../../../../platform/browser/browser-popup-utils";
import { PopupRouterCacheService } from "../../../../platform/popup/view-cache/popup-router-cache.service";

@Component({
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 3: Component should be standalone

Suggested change
@Component({
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,

changeDetection: ChangeDetectionStrategy.OnPush,
selector: "import-file-popout-dialog",
templateUrl: "./import-file-popout-dialog.component.html",
imports: [JslibModule, CommonModule, DialogModule, ButtonModule, TypographyModule],
})

Choose a reason for hiding this comment

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

standalone component

export class ImportFilePopoutDialogComponent {
constructor(
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 4: Use inject() instead of constructor injection

Suggested change
constructor(
private dialogService = inject(DialogService);
private popupRouterCacheService = inject(PopupRouterCacheService);

private dialogService: DialogService,
private popupRouterCacheService: PopupRouterCacheService,
) {}

async popOutWindow() {
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 5: Missing return type annotation

Suggested change
async popOutWindow() {
async popOutWindow(): Promise<void> {

await BrowserPopupUtils.openCurrentPagePopout(window);
}

async close() {
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 5: Missing return type annotation

Suggested change
async close() {
async close(): Promise<void> {

this.dialogService.closeAll();
// the current view exposes a file selector, ensure the view is popped to avoid using it outside of a popout
await this.popupRouterCacheService.back();
Copy link

Choose a reason for hiding this comment

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

โš ๏ธ Finding 1: Navigation logic may fail silently if back() has no history

The popupRouterCacheService.back() method will navigate to the root vault page if history is empty. This could be confusing UX if the user arrived at the import page through a direct link or bookmark rather than navigation.

Consider:

  1. Explicitly navigating to a known safe route (e.g., /vault-settings) instead of relying on back()
  2. Or add error handling if back() returns false or fails
Context from PopupRouterCacheService
async back() {
  // ...
  if (this.hasNavigated && history.length) {
    this.location.back();
    return;
  }
  // if no history is present, fallback to vault page
  await this.router.navigate([""]);
}

This fallback behavior may not match user expectations when canceling an import.

Copy link

Choose a reason for hiding this comment

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

โ™ป๏ธ Finding 3: Inconsistent navigation patterns create technical debt

Import uses popupRouterCacheService.back() while Send uses Router.navigate(["/tabs/send"]) (send-file-popout-dialog.component.ts:30). Both solve the same problem with different approaches.

Recommendation: Standardize navigation strategy across both features or extract shared logic to FilePopoutUtilsService.

Copy link
Member

Choose a reason for hiding this comment

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

question: This behaves differently from send, any reason why?

Copy link
Contributor Author

@harr1424 harr1424 Nov 19, 2025

Choose a reason for hiding this comment

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

This has been changed for consistency.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been changed for consistency.

This has been reverted back to use popupRouterCacheService.back(). Using router to instead navigate to a new views piles on views that can be "popped" off the stack. This creates a UI bug where clicking back repeatedly circles back to the same dialog presentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The difference in behavior is because the "Send" tab/view is on the root of the extension, this view doesn't present a back button in the navigation tab. Whereas the "Import Data" view is nested inside of the "Vault Settings" view which does present a back button in the navigation tab.

Copy link

Choose a reason for hiding this comment

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

๐Ÿ’ญ Navigation pattern differs from Send implementation

The Send dialog uses this.router.navigate(["/tabs/send"]) while this uses popupRouterCacheService.back().

While the author explained this is because Import is nested (has a back button) and Send is root-level, this difference creates maintenance burden.

Suggestion: Add a comment explaining why the navigation approach differs:

// Navigate back using router cache since Import view is nested under Vault Settings
// (Send uses explicit navigation because it's at root level with no back history)
await this.popupRouterCacheService.back();

}
}
Loading