Skip to content

Comments

feat(appLock): add logout flow to app lock modal [WPB-23186]#20391

Open
V-Gira wants to merge 20 commits intodevfrom
v/applock-sign-in-WPB-23186
Open

feat(appLock): add logout flow to app lock modal [WPB-23186]#20391
V-Gira wants to merge 20 commits intodevfrom
v/applock-sign-in-WPB-23186

Conversation

@V-Gira
Copy link
Contributor

@V-Gira V-Gira commented Feb 12, 2026

StoryWPB-23186 [Web] Applock forgot password - Users prompted to log out, log in, set new applock password

Pull Request

Summary

What is the change?

Implement a different logout flow when a user forgets their applock passcode

Previously:

  • user clicks "forgot passcode"
  • must enter their account password to delete their client on the backend
  • client is deleted, database is wiped, user is logged out

Now:

  • user clicks "forgot passcode"
  • user is offered to log out using the standard flow
  • user is offered to delete their local database
  • in all cases, the client remains on the backend (similar to the normal logout flow)
  • local database is only deleted if user chooses
  • applock is deactivated if not enforced by the team
  • user can log back in normaly by using their account password
  • if applock is enforced, user can choose a new passcode

Code changes

  • refactor to use UI-Kit components
  • UI changes according to new design, see figma
  • localization changes
  • removal of the client deletion flow
  • addition of the log out flow

Security Checklist (required)

  • External inputs are validated & sanitized on client and/or server where applicable.
  • API responses are validated; unexpected shapes are handled safely (fallbacks or errors).
  • No unsafe HTML is rendered; if unavoidable, sanitization is applied and documented where it happens.
  • Injection risks (XSS/SQL/command) are prevented via safe APIs and/or escaping.

Accessibility (required)

Standards Acknowledgement (required)


Screenshots or demo (if the user interface changed)

Forgot Passcode and log out

Kooha-2026-02-13-16-13-27

Create Passcode (Team enforced)

image

Create Passcode (not enforced)

image

Notes for reviewers

  • Trade-offs:
  • Follow-ups (linked issues):
  • Linked PRs (e.g. web-packages):

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

🔗 Download Full Report Artifact

🧪 Playwright Test Summary

  • Passed: 135
  • Failed: 19
  • Skipped: 12
  • 🔁 Flaky: 16
  • 📊 Total: 182
  • Total Runtime: 610.4s (~ 10 min 10 sec)
specs/AccountSettingsSpecs/accountSettings.spec.ts (❌ 0 failed, ⚠️ 1 flaky)
  • ⚠️ account settings > Verify link to manage a team is not shown when logged in as team member or normal use (tags: TC-1723, regression)
specs/AppLock/AppLock.spec.ts (❌ 2 failed, ⚠️ 0 flaky)
  • ❌ AppLock > Web: App should not lock if I switch back to webapp tab in time (during inactivity timeout) (tags: TC-2752, TC-2753, regression)
  • ❌ AppLock > Web: I want to unlock the app with passphrase after login (tags: TC-2754, TC-2755, TC-2758, TC-2763, regression)
specs/Authentication/authentication.spec.ts (❌ 0 failed, ⚠️ 1 flaky)
  • ⚠️ Authentication > Make sure user does not see data of user of previous sessions on same browser (tags: TC-1311, regression)
specs/Block/block.spec.ts (❌ 1 failed, ⚠️ 2 flaky)
  • ❌ Block: User A and User B are NOT in the same team > Verify you still receive messages from blocked person in a group chat (tags: TC-141, regression)
  • ⚠️ Block: User A and User B are NOT in the same team > Verify you can block a user who is not in your team (tags: TC-140, regression)
  • ⚠️ Block: User A and User B are NOT in the same team > Verify you can unblock someone from conversation list options (tags: TC-148, regression)
specs/Calling/calling.spec.ts (❌ 1 failed, ⚠️ 0 flaky)
  • ❌ Calling > Verify that current call is terminated if you want to call someone else (as caller) (tags: TC-2802, regression)
specs/CriticalFlow/addMembersToChat-TC-8631.spec.ts (❌ 0 failed, ⚠️ 1 flaky)
  • ⚠️ Team owner adds whole team to an all team chat (tags: TC-8631, crit-flow-web)
specs/CriticalFlow/joinTeam-TC-8635.spec.ts (❌ 1 failed, ⚠️ 0 flaky)
  • ❌ New person joins team and sets up device (tags: TC-8635, crit-flow-web)
specs/Delete/delete.spec.ts (❌ 3 failed, ⚠️ 0 flaky)
  • ❌ Delete > Message gets deleted even when I was offline on time of deletion (tags: TC-573, regression)
  • ❌ Delete > Delete "For Everyone" works for location sharing (tags: TC-582, regression)
  • ❌ Delete > I see no unread count if a message was deleted from someone in a conversation (tags: TC-587, regression)
specs/Drive/editMultipartMessage-TC-8786.spec.ts (❌ 0 failed, ⚠️ 1 flaky)
  • ⚠️ Edit multipart message in a group conversation (tags: crit-flow-cells, regression, TC-8786)
specs/Edit/edit.spec.ts (❌ 0 failed, ⚠️ 1 flaky)
  • ⚠️ Edit > I cannot edit another users message (tags: TC-683, regression)
specs/HistoryBackup/historyBackup.spec.ts (❌ 3 failed, ⚠️ 1 flaky)
  • ❌ History Backup > I want to import a backup that I exported when I was using a different email/password (tags: TC-118, regression)
  • ❌ History Backup > I should not be able to import a backup with wrong password (tags: TC-135, regression)
  • ❌ History Backup > I should not see the deleted group after restore from the backup (tags: TC-1097, regression)
  • ⚠️ History Backup > I should not be able to restore from the history of another person's account (tags: TC-125, regression)
specs/Markdown/markdown.spec.ts (❌ 1 failed, ⚠️ 1 flaky)
  • ❌ Markdown > I want to write a emphasized message (tags: TC-1314, regression)
  • ⚠️ Markdown > I want to write a code message (tags: TC-1315, regression)
specs/noInternetCallGuard.spec.ts (❌ 1 failed, ⚠️ 0 flaky)
  • ❌ Starting call 1:1 call without internet (tags: )
specs/Reactions/reactions.spec.ts (❌ 3 failed, ⚠️ 2 flaky)
  • ❌ Reactions > Verify liking someone's link preview in 1:1 (tags: TC-1527, regression)
  • ❌ Reactions > Verify liking someone's location (tags: TC-1533, regression)
  • ❌ Reactions > I want to add/remove a reaction by clicking a reaction pill (tags: TC-1548, regression)
  • ⚠️ Reactions > Verify liking someone's picture (tags: TC-1528, regression)
  • ⚠️ Reactions > I want to add/remove a reaction using emoji picker (tags: TC-1549, regression)
specs/Reply/reply.spec.ts (❌ 1 failed, ⚠️ 2 flaky)
  • ❌ Reply > I want to reply to a location share (tags: TC-3009, regression)
  • ⚠️ Reply > I want to see a placeholder text as quote when original message is not available anymore (tags: TC-2994, regression)
  • ⚠️ Reply > I want to reply to an audio message (tags: TC-3003, regression)
specs/SelfDeletingMessages/selfDeletingMessages.spec.ts (❌ 2 failed, ⚠️ 2 flaky)
  • ❌ Self Deleting Messages > Verify sending ephemeral text message in 1:1 (tags: TC-657, regression)
  • ❌ set globally in group conversation > I want to see the ephemeral indicator is updated in the input field if someone sets a global timer in conversation options (tags: TC-3719, regression)
  • ⚠️ Self Deleting Messages > Verify that message with previous timer are deleted on start-up when the timeout passed in 1:1 (tags: TC-664, regression)
  • ⚠️ Self Deleting Messages > Verify the message is not deleted for users that didn't read the message (tags: TC-675, regression)
specs/Smokes/smoke.spec.ts (❌ 0 failed, ⚠️ 1 flaky)
  • ⚠️ Login and search smoke (tags: smoke)

@V-Gira V-Gira marked this pull request as ready for review February 13, 2026 16:53
@V-Gira V-Gira requested review from a team and otto-the-bot as code owners February 13, 2026 16:53
Copilot AI review requested due to automatic review settings February 13, 2026 16:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a new logout flow for the app lock feature, replacing the previous client deletion flow with a standard logout that preserves the client on the backend.

Changes:

  • Users who forget their applock passcode can now log out using the standard flow instead of deleting their client
  • The applock feature is disabled (if not enforced) when logging out via the forgot passcode flow
  • UI has been refactored to use @wireapp/react-ui-kit components (Button, Input, Checkbox, Link)
  • Removed the client deletion flow (WIPE_CONFIRM and WIPE_PASSWORD states)
  • Added new LOGOUT state with an option to clear local data
  • Updated localization strings to reflect the new flow

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
apps/webapp/src/types/i18n.d.ts Updated type definitions for new and modified i18n strings
apps/webapp/src/i18n/en-US.json Added/updated localization strings for new logout flow, removed old wipe-related strings
apps/webapp/src/script/repositories/user/AppLockRepository.ts Extracted disableFeature method for reuse in logout flow
apps/webapp/src/script/page/AppLock/Applock.styles.ts New styles file for AppLock component using CSS-in-JS
apps/webapp/src/script/page/AppLock/AppLock.tsx Major refactor: removed WIPE states, added LOGOUT state, migrated to UI-Kit components, implemented new logout flow

Comment on lines +467 to +476
<Link
variant={LinkVariant.PRIMARY}
type="button"
css={applockStyles.linkStyle}
className="button-reset-default block modal__cta"
data-uie-name="go-forgot-passphrase"
onClick={onClickForgot}
>
{t('modalAppLockLockedForgotCTA')}
</button>

<div className="modal__buttons">
<button
type="submit"
className="modal__button modal__button--primary modal__button--full"
data-uie-name="do-action"
>
{t('modalAppLockLockedUnlockButton')}
</button>
</div>
</Link>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The Link component is used with type="button" which creates a semantic mismatch. A Link component should be used for navigation (anchor tags), not for button actions like onClick handlers. This component triggers onClickForgot which changes state rather than navigating.

Consider using a Button component with ButtonVariant.TERTIARY or similar styling instead of Link for this action, or use a native <button> element styled as a link. This will ensure proper semantics for screen readers and keyboard navigation.

Copilot uses AI. Check for mistakes.
Comment on lines +482 to +483
<p>{t('modalAppLockForgotMessage')}</p>
<br />
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Using <br /> for spacing (line 483) is semantically incorrect and bad for accessibility. The <br /> element should only be used for line breaks within content (like in addresses or poems), not for spacing between paragraphs.

Instead, use proper paragraph elements with CSS margins for spacing:

<p css={{marginBottom: '16px'}}>{t('modalAppLockForgotMessage')}</p>
<p>{t('modalAppLockForgotSecondMessage')}</p>

Or define the spacing in the styles file and apply it to the paragraph elements.

Suggested change
<p>{t('modalAppLockForgotMessage')}</p>
<br />
<p css={{marginBottom: '16px'}}>{t('modalAppLockForgotMessage')}</p>

Copilot uses AI. Check for mistakes.
Comment on lines +236 to +239
if (isTemporaryClient) {
await clientRepository.logoutClient();
} else {
setState(APPLOCK_STATE.LOGOUT);
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Missing error handling in async function onClickLogout. Line 237 calls await clientRepository.logoutClient() but there's no try-catch block to handle potential errors.

If logoutClient() throws an error, it will be an unhandled promise rejection which could leave the UI in an inconsistent state (modal still showing, user confused about their logged-in status).

Add proper error handling:

const onClickLogout = async () => {
  try {
    if (isTemporaryClient) {
      await clientRepository.logoutClient();
    } else {
      setState(APPLOCK_STATE.LOGOUT);
    }
  } catch (error) {
    // Handle error appropriately - show error message or log
    logger.error('Failed to logout client', error);
    // Optionally show user-facing error
  }
};
Suggested change
if (isTemporaryClient) {
await clientRepository.logoutClient();
} else {
setState(APPLOCK_STATE.LOGOUT);
try {
if (isTemporaryClient) {
await clientRepository.logoutClient();
} else {
setState(APPLOCK_STATE.LOGOUT);
}
} catch (error) {
// [Important] Handle logout errors to avoid unhandled promise rejections and inconsistent UI state.
// Consider surfacing a user-visible error message if appropriate for this UI.
// eslint-disable-next-line no-console
console.error('Failed to logout client', error);

Copilot uses AI. Check for mistakes.
Comment on lines +501 to 532
{state === APPLOCK_STATE.LOGOUT && (
<Fragment>
<div className="modal__text" data-uie-name="label-applock-wipe-confirm-text">
{t('modalAppLockWipeConfirmMessage')}
</div>

<div className="modal__buttons">
<button onClick={onGoBack} className="modal__button modal__button--secondary" data-uie-name="do-go-back">
{t('modalAppLockWipeConfirmGoBackButton')}
</button>

<button
onClick={onClickWipeConfirm}
className="modal__button modal__button--primary modal__button--alert"
data-uie-name="do-action"
>
{t('modalAppLockWipeConfirmConfirmButton')}
</button>
</div>
</Fragment>
)}

{state === APPLOCK_STATE.WIPE_PASSWORD && (
<form onSubmit={onWipeDatabase}>
<input
aria-label={t('modalAppLockWipePasswordTitle', {brandName: Config.getConfig().BRAND_NAME})}
autoFocus
className="modal__input"
type="password"
name="password"
autoComplete="new-password"
placeholder={t('modalAppLockWipePasswordPlaceholder')}
onKeyDown={clearWipeError}
data-uie-name="input-applock-wipe"
/>

<p className="modal__input__error" style={{height: 20}} data-uie-name="label-applock-wipe-error">
{wipeError}
</p>

<div className="modal__buttons">
<button
type="button"
<Checkbox
checked={clearData}
data-uie-name="modal-option-checkbox"
id="clear-data-checkbox"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.checked;
setClearData(value);
}}
>
<CheckboxLabel className="label-xs" htmlFor="clear-data-checkbox">
{t('modalAccountLogoutOption')}
</CheckboxLabel>
</Checkbox>

<div css={applockStyles.buttonGroupStyle}>
<Button
css={applockStyles.buttonStyle}
variant={ButtonVariant.SECONDARY}
onClick={onGoBack}
className="modal__button modal__button--secondary"
data-uie-name="do-go-back"
>
{t('modalAppLockWipePasswordGoBackButton')}
</button>
{t('modalAppLockLogoutCancelButton')}
</Button>

<button
type="submit"
className="modal__button modal__button--primary modal__button--alert"
data-uie-name="do-action"
>
{t('modalAppLockWipePasswordConfirmButton')}
</button>
<Button css={applockStyles.buttonStyle} onClick={() => onLogout(clearData)} data-uie-name="do-action">
{t('modalAccountLogoutAction')}
</Button>
</div>
</form>
</Fragment>
)}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Missing test coverage for new APPLOCK_STATE.LOGOUT state. The new logout flow introduced in this PR (lines 46, 259, 501-532) is a significant change to the applock behavior, but there are no corresponding tests in AppLock.test.tsx.

Add tests to cover:

  1. Transitioning to LOGOUT state when "Forgot passcode?" → "Log Out" is clicked
  2. The checkbox for clearing data functionality
  3. The "Cancel" button returning to LOCKED state
  4. The "Log Out" button triggering logout with correct clearData value
  5. Temporary client handling (direct logout without showing LOGOUT state)

This is important because the logout flow affects user data and authentication state.

Copilot uses AI. Check for mistakes.
'isAppLockEnforced',
]);

const isTemporaryClient = clientState.currentClient?.isTemporary();
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Potential null pointer exception: clientState.currentClient?.isTemporary() on line 87 can return undefined if currentClient is not set. The code checks isTemporaryClient on line 236 without null handling.

If clientState.currentClient is undefined, isTemporaryClient will be undefined, which will cause the condition if (isTemporaryClient) to evaluate to false. However, this creates ambiguity - undefined being treated as false may mask cases where the client state hasn't been initialized properly.

Consider adding explicit null checks or default values, such as:
const isTemporaryClient = clientState.currentClient?.isTemporary() ?? false;

Suggested change
const isTemporaryClient = clientState.currentClient?.isTemporary();
const isTemporaryClient = clientState.currentClient?.isTemporary() ?? false;

Copilot uses AI. Check for mistakes.
Comment on lines 299 to 303
<p
className="modal__text"
dangerouslySetInnerHTML={{__html: t('modalAppLockSetupMessage', undefined, {br: '<br><br>'})}}
data-uie-name="label-applock-set-text"
/>
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Unsafe use of dangerouslySetInnerHTML without sanitization on line 301. While this uses a localization string (t('modalAppLockSetupMessage')), the {br: '<br><br>'} substitution injects raw HTML without sanitization.

If the localization strings are controlled and trusted, document this with a comment explaining:

  1. That the content is from trusted localization files
  2. That only <br> tags are allowed in the substitution

However, consider using safer alternatives like:

  • React components for line breaks
  • CSS for spacing
  • A safe HTML sanitizer like DOMPurify if HTML is necessary

This same pattern appears throughout the component (lines 301, 399) and should be addressed consistently.

Copilot generated this review using guidance from repository custom instructions.
data-uie-name="input-applock-unlock"
autoComplete="new-password"
autoComplete="current-password"
error={ErrorMessage()}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

The ErrorMessage component is defined as a function component but is used as a function call error={ErrorMessage()} on line 460, rather than as a React element error={<ErrorMessage />}.

While this works in this case since it returns JSX, this is an anti-pattern in React. Components should be used as JSX elements, not called as functions. This affects proper React reconciliation, hooks behavior, and component lifecycle.

Change the usage to pass the component as JSX:

error={<ErrorMessage />}

Or if the Input component expects a function that returns JSX, rename ErrorMessage to renderErrorMessage or getErrorMessage to indicate it's a render function, not a React component.

Suggested change
error={ErrorMessage()}
error={<ErrorMessage />}

Copilot uses AI. Check for mistakes.
Comment on lines 307 to 450
@@ -365,26 +360,28 @@
{t('modalAppLockSetupSpecial')}
</p>

<div className="modal__buttons">
<div css={applockStyles.buttonGroupStyle}>
{!isAppLockEnforced && (
<button
<Button
css={applockStyles.buttonStyle}
variant={ButtonVariant.SECONDARY}
type="button"
className="modal__button modal__button--secondary"
data-uie-name="do-cancel-applock"
onClick={onCancelAppLock}
>
{t('modalConfirmSecondary')}
</button>
</Button>
)}

<button
<Button
css={!isAppLockEnforced ? applockStyles.buttonStyle : undefined}
block={isAppLockEnforced}
type="submit"
className="modal__button modal__button--primary modal__button--full"
data-uie-name="do-action"
disabled={!isSetupPassphraseValid}
>
{t('modalAppLockSetupAcceptButton')}
</button>
</Button>
</div>
</form>
)}
@@ -403,17 +400,15 @@
data-uie-name="label-applock-set-text"
/>

<div className="modal__text modal__label" data-uie-name="label-applock-unlock-text">
{t('modalAppLockPasscode')}
</div>

<input
<Input
aria-label={t('modalAppLockSetupChangeTitle', {brandName: Config.getConfig().BRAND_NAME})}
autoFocus
className="modal__input"
label={t('modalAppLockPasscode')}
type="password"
placeholder={t('modalAppLockInputPlaceholder')}
value={setupPassphrase}
onChange={event => setSetupPassphrase(event.target.value)}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setSetupPassphrase(event.target.value)}
data-uie-status={isSetupPassphraseValid ? 'valid' : 'invalid'}
data-uie-name="input-applock-set-a"
autoComplete="new-password"
@@ -442,147 +437,98 @@
</p>

<div className="modal__buttons">
<button
type="submit"
className="modal__button modal__button--primary modal__button--full"
data-uie-name="do-action"
disabled={!isSetupPassphraseValid}
>
<Button block type="submit" data-uie-name="do-action" disabled={!isSetupPassphraseValid}>
{t('modalAppLockSetupAcceptButton')}
</button>
</Button>
</div>
</form>
)}

{state === APPLOCK_STATE.LOCKED && (
<form onSubmit={onUnlock}>
<div className="modal__text modal__label" data-uie-name="label-applock-unlock-text">
{t('modalAppLockPasscode')}
</div>

<input
<Input
aria-label={t('modalAppLockLockedTitle', {brandName: Config.getConfig().BRAND_NAME})}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Redundant aria-label on Input component. Lines 307, 404, and 450 all use both aria-label and label props on the Input component.

The label prop from the UI-Kit Input component already provides proper labeling through an associated <label> element. Adding aria-label creates redundancy and can confuse screen readers about which label to announce.

Remove the aria-label prop and rely on the label prop:

<Input
  label={t('modalAppLockPasscode')}
  type="password"
  // ... other props
/>

The aria-label should only be used when you need to override or provide a label that differs from the visible label, which doesn't appear to be the case here.

Copilot generated this review using guidance from repository custom instructions.
@V-Gira V-Gira force-pushed the v/applock-sign-in-WPB-23186 branch from b416a9e to cd4bdf4 Compare February 13, 2026 17:27
@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

❌ Patch coverage is 41.93548% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 45.26%. Comparing base (a93dcd7) to head (80e3bbc).

Files with missing lines Patch % Lines
apps/webapp/src/script/page/AppLock/AppLock.tsx 28.57% 13 Missing and 2 partials ⚠️
.../src/script/repositories/user/AppLockRepository.ts 25.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #20391      +/-   ##
==========================================
+ Coverage   45.25%   45.26%   +0.01%     
==========================================
  Files        1633     1634       +1     
  Lines       40245    40249       +4     
  Branches     8325     8326       +1     
==========================================
+ Hits        18212    18219       +7     
+ Misses      20105    20102       -3     
  Partials     1928     1928              
Flag Coverage Δ
app_webapp 43.44% <41.93%> (+0.01%) ⬆️
lib_api_client 50.17% <ø> (ø)
lib_core 58.86% <ø> (ø)

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

Files with missing lines Coverage Δ
...s/webapp/src/script/page/AppLock/Applock.styles.ts 100.00% <100.00%> (ø)
.../src/script/repositories/user/AppLockRepository.ts 49.01% <25.00%> (+1.96%) ⬆️
apps/webapp/src/script/page/AppLock/AppLock.tsx 56.00% <28.57%> (+0.73%) ⬆️
🚀 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.

Copilot AI review requested due to automatic review settings February 15, 2026 16:03
@sonarqubecloud
Copy link

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.

Comment on lines +235 to +241
const onClickLogout = async () => {
if (isTemporaryClient) {
await clientRepository.logoutClient();
} else {
setState(APPLOCK_STATE.LOGOUT);
}
};
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The isTemporaryClient check uses optional chaining which will return undefined if currentClient is undefined. In the onClickLogout function at line 236, this means if (isTemporaryClient) will be false when isTemporaryClient is undefined, which may not be the intended behavior.

Consider making the condition more explicit:

if (isTemporaryClient === true) {
  await clientRepository.logoutClient();
} else {
  setState(APPLOCK_STATE.LOGOUT);
}

This ensures we only call logoutClient when we explicitly know the client is temporary, and show the logout modal in all other cases (including when currentClient is undefined).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +56
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {CSSObject} from '@emotion/react';

const buttonGroupStyle: CSSObject = {
margin: '16px 0',
display: 'flex',
justifyContent: 'space-around',
};

const buttonStyle: CSSObject = {
width: '160px',
};

const headerStyle: CSSObject = {
marginBottom: '32px',
fontSize: 'var(--font-size-large)',
textTransform: 'initial',
};

const linkStyle: CSSObject = {
margin: '32px 0',
fontSize: 'var(--font-size-base)',
fontWeight: 'var(--font-weight-bold)',
display: 'flex',
justifyContent: 'center',
};

const unlockButtonStyle: CSSObject = {
margin: '16px 0',
};

export const applockStyles = {
buttonGroupStyle,
buttonStyle,
headerStyle,
linkStyle,
unlockButtonStyle,
};
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

File name has inconsistent casing. The file is named Applock.styles.ts (lowercase 'l') while the main component is AppLock.tsx (uppercase 'L'). For consistency, consider renaming to AppLock.styles.ts to match the component name casing.

Copilot uses AI. Check for mistakes.
"modalAppLockForgotWipeCTA": "Reset this client",
"modalAppLockForgotGoBackButton": "Back",
"modalAppLockForgotMessage": "The data stored on this device can only be accessed with your app lock passcode.",
"modalAppLockForgotSecondMessage":"If you have forgotten your passcode, you can log out of this account and set a new passcode the next time you log in.",
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

Missing space after the colon in the JSON property. Should be "modalAppLockForgotSecondMessage": "If you have forgotten..." (note the space after the colon).

Suggested change
"modalAppLockForgotSecondMessage":"If you have forgotten your passcode, you can log out of this account and set a new passcode the next time you log in.",
"modalAppLockForgotSecondMessage": "If you have forgotten your passcode, you can log out of this account and set a new passcode the next time you log in.",

Copilot uses AI. Check for mistakes.
Comment on lines 212 to 241
@@ -236,12 +229,16 @@ const AppLock = ({
const isSetupPassphraseLength = passwordRegexLength.test(setupPassphrase);
const isSetupPassphraseSpecial = passwordRegexSpecial.test(setupPassphrase);

const clearWipeError = () => setWipeError('');
const clearUnlockError = () => setUnlockError('');
const onGoBack = () => setState(APPLOCK_STATE.LOCKED);
const onClickForgot = () => setState(APPLOCK_STATE.FORGOT);
const onClickWipe = () => setState(APPLOCK_STATE.WIPE_CONFIRM);
const onClickWipeConfirm = () => setState(APPLOCK_STATE.WIPE_PASSWORD);
const onClickLogout = async () => {
if (isTemporaryClient) {
await clientRepository.logoutClient();
} else {
setState(APPLOCK_STATE.LOGOUT);
}
};
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The new LOGOUT state and onLogout flow lack test coverage. Tests should verify:

  1. When a temporary client clicks "Forgot passcode", it directly calls clientRepository.logoutClient()
  2. When a non-temporary client clicks "Forgot passcode", it transitions to the LOGOUT state
  3. The onLogout function disables app lock only when not enforced
  4. The onLogout function removes the passcode code
  5. The onLogout function publishes the correct SIGN_OUT event with the clearData flag

Consider adding test cases covering these scenarios to ensure the logout flow works correctly for both temporary and permanent clients.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants