Skip to content

Emergency Access Code Cleanup#426

Merged
SailReal merged 18 commits intodevelopfrom
feature/ea-cleanup
Apr 1, 2026
Merged

Emergency Access Code Cleanup#426
SailReal merged 18 commits intodevelopfrom
feature/ea-cleanup

Conversation

@overheadhunter
Copy link
Copy Markdown
Member

This pull request refactors the emergency access setup flow in the vault creation and management UI. The main change is the extraction and reuse of the new EmergencyAccessSetup component, which centralizes logic and UI for configuring emergency council members and required key shares. This simplifies the parent components, reduces code duplication, and improves maintainability. Several related UI and logic improvements are also included.

Key changes:

Emergency Access Setup Refactor:

  • Replaced inline emergency access configuration logic and UI in CreateVault.vue and EmergencyAccessDialog.vue with the new EmergencyAccessSetup component. This component now handles council member selection, key share requirements, and related validation, streamlining the parent components. [1] [2] [3] [4] [5]

  • Updated emergency access validation and key splitting logic in CreateVault.vue to delegate to the EmergencyAccessSetup component, removing redundant state and methods. [1] [2]

UI and Usability Improvements:

  • Improved disabled and readonly states for emergency council member selection, including visual cues (opacity, cursor) in MultiUserSelectInputGroup.vue. [1] [2] [3]

  • Cleaned up imports and removed unused code from affected components for clarity and maintainability. [1] [2] [3]

Bug Fixes and Minor Enhancements:

  • Fixed a potential null state issue by ensuring the emergency access step only renders when vaultKeys are present in CreateVault.vue.

  • Updated button disabling logic to use validation state from EmergencyAccessSetup, ensuring users cannot proceed with invalid emergency access settings.

Other:

  • Minor UI refinements for council member pills and error handling in MultiUserSelectInputGroup.vue. [1] [2]

These changes collectively improve the modularity, reusability, and user experience of the emergency access configuration flow.

@overheadhunter overheadhunter requested a review from Copilot March 23, 2026 13:16
@overheadhunter overheadhunter requested a review from mindmonk March 23, 2026 13:17
@overheadhunter overheadhunter marked this pull request as ready for review March 23, 2026 13:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR factors emergency council selection, validation, and recovery-key splitting into a new Vue 3 component, EmergencyAccessSetup.vue. CreateVault, EmergencyAccessDialog, GrantEmergencyAccessDialog, and other callers now mount EmergencyAccessSetup and delegate validation/splitting to it; local council-selection state, redundancy checks, and splitting helpers were removed. EmergencyScenarioVisualization was refactored to a slot-lane layout. Vault creation and grant flows now consume requiredEmergencyKeyShares/emergencyKeyShares produced by EmergencyAccessSetup. Minor styling tweaks applied to MultiUserSelectInputGroup and conditional rendering updated where settings are required.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mindmonk
  • SailReal
  • tobihagemann
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Emergency Access Code Cleanup' is vague and generic, using non-descriptive terms that don't convey specific information about the primary changes. Use a more descriptive title that reflects the main refactoring, such as 'Refactor emergency access setup into reusable EmergencyAccessSetup component' or 'Extract emergency council configuration logic into EmergencyAccessSetup component'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the emergency access refactoring, UI improvements, bug fixes, and code cleanup across multiple components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/ea-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue (1)

55-55: Remove unused isReadonly import.

The isReadonly function is imported from Vue but never used in this component.

🧹 Proposed fix
-import { ArrowUturnLeftIcon, ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
-import { useId, computed, onMounted, ref, isReadonly } from 'vue';
+import { ArrowUturnLeftIcon, ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
+import { useId, computed, onMounted, ref } from 'vue';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue` at line 55,
Remove the unused isReadonly import from the Vue import list in the component;
update the import statement that currently reads "import { useId, computed,
onMounted, ref, isReadonly } from 'vue';" to exclude isReadonly so it becomes
"import { useId, computed, onMounted, ref } from 'vue';" and ensure no other
references to isReadonly exist in EmergencyAccessSetup.vue.
frontend/src/components/emergencyaccess/EmergencyScenarioVisualization.vue (1)

63-63: Consider using ref for timeoutId for consistency.

timeoutId is declared as a plain let variable while randomSelectionInterval uses ref. Both work correctly, but using ref for both would be more consistent with Vue's reactive patterns in this component.

♻️ Optional: Use ref for timeoutId
-let timeoutId: ReturnType<typeof setTimeout> | undefined;
+const timeoutId = ref<ReturnType<typeof setTimeout>>();

Then update usage:

-    if (timeoutId !== undefined) {
-      clearTimeout(timeoutId);
+    if (timeoutId.value !== undefined) {
+      clearTimeout(timeoutId.value);
     }
-    timeoutId = setTimeout(() => {
+    timeoutId.value = setTimeout(() => {
       loadingCouncilSelection.value = false;
-      timeoutId = undefined;
+      timeoutId.value = undefined;
     }, 100);

Also applies to: 122-128

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/emergencyaccess/EmergencyScenarioVisualization.vue`
at line 63, Convert the plain let timeoutId to a Vue ref for consistency with
the existing reactive pattern (like randomSelectionInterval): replace the
top-level declaration of timeoutId with a ref (e.g., const timeoutId =
ref<ReturnType<typeof setTimeout> | undefined>(undefined)) and then update all
usages where timeoutId is read, assigned, cleared (clearTimeout) to access or
set timeoutId.value; ensure functions that set or clear the timer (the same
places referenced around lines 122-128 and where randomSelectionInterval is
used) use timeoutId.value so reactivity/consistency is maintained.
frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue (1)

62-62: Consider defaulting to true when emergencyAccessSetup is not yet mounted.

The button uses emergencyAccessSetup?.hasValidationErrors which evaluates to undefined (falsy) before the component mounts, potentially allowing the button to be briefly enabled. While the dialog transition likely prevents immediate user interaction, an explicit default would be safer.

🛡️ Safer disabled state
-                  :disabled="emergencyAccessSetup?.hasValidationErrors"
+                  :disabled="emergencyAccessSetup?.hasValidationErrors ?? true"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue` at
line 62, The disabled binding currently uses
emergencyAccessSetup?.hasValidationErrors which is undefined before mount and
can briefly enable the button; change the binding to explicitly default to true
when emergencyAccessSetup is not yet available (e.g., use a nullish-coalescing
or ternary to evaluate emergencyAccessSetup?.hasValidationErrors and fall back
to true) so that the button remains disabled until the setup object is mounted;
update the template reference to emergencyAccessSetup and hasValidationErrors
accordingly.
frontend/src/components/CreateVault.vue (1)

140-140: Same optional chaining consideration as GrantEmergencyAccessDialog.

Consider using emergencyAccessSetup?.hasValidationErrors ?? true to ensure the button is disabled before the component mounts.

🛡️ Safer disabled state
-                  :disabled="emergencyAccessSetup?.hasValidationErrors || processing"
+                  :disabled="(emergencyAccessSetup?.hasValidationErrors ?? true) || processing"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/CreateVault.vue` at line 140, Replace the current
disabled expression that uses optional chaining with a safe default so the
button is disabled before the component mounts: change the binding that
references emergencyAccessSetup?.hasValidationErrors (in CreateVault.vue) to use
a nullish-coalescing default true for missing state and then OR with processing
— i.e., use emergencyAccessSetup?.hasValidationErrors ?? true combined with
processing so the button remains disabled until validity is known.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue`:
- Around line 40-44: The template is passing an undeclared prop :min-members to
EmergencyScenarioVisualization; remove the :min-members="minMembers" attribute
from the EmergencyScenarioVisualization usage (leave
:selected-users="emergencyCouncilMembers" and
:required-key-shares="requiredKeyShares" unchanged) so only the declared props
selectedUsers and requiredKeyShares are passed and Vue will not emit a non-prop
attribute warning.

---

Nitpick comments:
In `@frontend/src/components/CreateVault.vue`:
- Line 140: Replace the current disabled expression that uses optional chaining
with a safe default so the button is disabled before the component mounts:
change the binding that references emergencyAccessSetup?.hasValidationErrors (in
CreateVault.vue) to use a nullish-coalescing default true for missing state and
then OR with processing — i.e., use emergencyAccessSetup?.hasValidationErrors ??
true combined with processing so the button remains disabled until validity is
known.

In `@frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue`:
- Line 55: Remove the unused isReadonly import from the Vue import list in the
component; update the import statement that currently reads "import { useId,
computed, onMounted, ref, isReadonly } from 'vue';" to exclude isReadonly so it
becomes "import { useId, computed, onMounted, ref } from 'vue';" and ensure no
other references to isReadonly exist in EmergencyAccessSetup.vue.

In `@frontend/src/components/emergencyaccess/EmergencyScenarioVisualization.vue`:
- Line 63: Convert the plain let timeoutId to a Vue ref for consistency with the
existing reactive pattern (like randomSelectionInterval): replace the top-level
declaration of timeoutId with a ref (e.g., const timeoutId =
ref<ReturnType<typeof setTimeout> | undefined>(undefined)) and then update all
usages where timeoutId is read, assigned, cleared (clearTimeout) to access or
set timeoutId.value; ensure functions that set or clear the timer (the same
places referenced around lines 122-128 and where randomSelectionInterval is
used) use timeoutId.value so reactivity/consistency is maintained.

In `@frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue`:
- Line 62: The disabled binding currently uses
emergencyAccessSetup?.hasValidationErrors which is undefined before mount and
can briefly enable the button; change the binding to explicitly default to true
when emergencyAccessSetup is not yet available (e.g., use a nullish-coalescing
or ternary to evaluate emergencyAccessSetup?.hasValidationErrors and fall back
to true) so that the button remains disabled until the setup object is mounted;
update the template reference to emergencyAccessSetup and hasValidationErrors
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c36cb7ee-1db9-4a76-b460-a974a19c33d4

📥 Commits

Reviewing files that changed from the base of the PR and between a29d65d and 36c5d0b.

📒 Files selected for processing (6)
  • frontend/src/components/CreateVault.vue
  • frontend/src/components/MultiUserSelectInputGroup.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue
  • frontend/src/components/emergencyaccess/EmergencyScenarioVisualization.vue
  • frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue

Copy link
Copy Markdown
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 refactors the emergency access configuration flows by extracting a reusable EmergencyAccessSetup component and integrating it into vault creation and emergency access dialogs, while also improving related UI components.

Changes:

  • Introduced EmergencyAccessSetup to centralize emergency council selection, validation, and key-splitting.
  • Replaced duplicated inline emergency access UI/logic in CreateVault.vue, GrantEmergencyAccessDialog.vue, and EmergencyAccessDialog.vue.
  • Updated emergency access visualization/UI states (EmergencyScenarioVisualization.vue, MultiUserSelectInputGroup.vue) for improved UX.

Reviewed changes

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

Show a summary per file
File Description
frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue New shared component encapsulating emergency access council selection + validation + key splitting.
frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue Replaced inline council/key-share logic with EmergencyAccessSetup.
frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue Replaced council-change UI with EmergencyAccessSetup and delegated validation/state.
frontend/src/components/CreateVault.vue Delegated emergency access setup/validation/splitting to EmergencyAccessSetup.
frontend/src/components/emergencyaccess/EmergencyScenarioVisualization.vue Refactored visualization rendering/animation and interval logic.
frontend/src/components/MultiUserSelectInputGroup.vue Improved readonly/disabled styling and pill layout.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mindmonk
Copy link
Copy Markdown
Contributor

mindmonk commented Mar 23, 2026

@overheadhunter Is it intentional that the Emergency Access Vault Council can no longer choose its own members when “Let vault owners choose different keyholders” is disabled?

My understanding was that this restriction should only apply to vault owners, as also described in the text.

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 24, 2026

@tobihagemann I've opened a new pull request, #429, to work on those changes. Once the pull request is ready, I'll request review from you.

@overheadhunter
Copy link
Copy Markdown
Member Author

@overheadhunter Is it intentional that the Emergency Access Vault Council can no longer choose its own members when “Let vault owners choose different keyholders” is disabled?

My understanding was that this restriction should only apply to vault owners, as also described in the text.

good catch, fixed in latest version

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/CreateVault.vue`:
- Around line 344-349: When loadDefaultEmergencyAccessSettings catches an error
from backend.settings.get(), don't leave settings.value undefined (which is
treated as "disabled"); instead either set settings.value to a safe sentinel
that enforces emergency access (e.g., an object with emergencyAccessEnabled:
true) or set a settingsLoadError flag and make the create-vault flow check that
flag and block progression until settings are successfully reloaded. Update
loadDefaultEmergencyAccessSettings to assign that sentinel/flag on error and
update the downstream checks that read settings.value (the vault creation flow
in CreateVault.vue) to treat the sentinel/flag as "must block and retry" rather
than "disabled".

In `@frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue`:
- Around line 740-742: Split the logic that treats COUNCIL_CHANGE as unsupported
when there are too few new members: first validate process.type ===
'COUNCIL_CHANGE' and then explicitly check that councilMembers.value has at
least process.details.newRequiredKeyShares (and that all selected councilMembers
resolve to activated users); if that check fails throw a dedicated "insufficient
council members" (or similarly named) error instead of falling through to the
generic unsupported-type path so callers can diagnose completion failures;
update the block around EmergencyAccess.split and
backend.vaults.createOrUpdateVault to only execute when both the type is
COUNCIL_CHANGE and the sufficiency/activation check passes.

In `@frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue`:
- Around line 67-77: The component currently always overwrites the displayed
requiredKeyShares from backend.settings.get() inside initialize(), which
prevents callers from injecting a process-specific stored value; add an optional
prop (e.g., requiredKeyShares or initialRequiredKeyShares) to the defineProps
list and default to undefined, then change initialize() and the code at the
other spot that sets requiredKeyShares (references: initialize(),
requiredKeyShares) to only read backend.settings.get() when that prop is
undefined; callers rendering an existing council-change process should pass the
stored newRequiredKeyShares via this new prop so the view shows the process
value instead of the live default.
- Around line 125-136: The split function currently only rejects when
emergencyCouncilMembers.value.length < requiredKeyShares.value, allowing callers
to bypass the UI-enforced minimum; update split (the async function split and
its checks around requiredKeyShares and emergencyCouncilMembers) to also
validate against minMembers (e.g., if emergencyCouncilMembers.value.length <
minMembers.value) and throw the same localized error (use
t('grantEmergencyAccessDialog.error.tooFewCouncilMembers') or the appropriate
i18n key) so the API enforces the component's minMembers rule.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ceadd285-473a-46df-990d-96504659d62c

📥 Commits

Reviewing files that changed from the base of the PR and between 36c5d0b and 63a8d6d.

📒 Files selected for processing (5)
  • frontend/src/components/CreateVault.vue
  • frontend/src/components/VaultDetails.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue
  • frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/components/CreateVault.vue (1)

109-140: ⚠️ Potential issue | 🟡 Minor

Mirror the child validation check inside the submit handler.

Line 140 disables the button, but the form can still be submitted from inside EmergencyAccessSetup, and Line 436 will call split() even while validation errors are present. Return early here before invoking the child API so keyboard/programmatic submits can’t bypass the validation state.

💡 Suggested change
 async function validateVaultEmergencyAccess() {
   onCreateError.value = undefined;
   if (!emergencyAccessSetup.value || !vaultKeys.value) {
     onCreateError.value = new Error('Invalid state.');
     return;
   }
+  if (emergencyAccessSetup.value.hasValidationErrors) {
+    return;
+  }
   
   processing.value = true;
   try {

Also applies to: 427-445

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/CreateVault.vue` around lines 109 - 140, The submit
handler validateVaultEmergencyAccess() must mirror the disabled-button guard by
checking the EmergencyAccessSetup ref for existence and for hasValidationErrors
and return early if validation fails; update validateVaultEmergencyAccess() to
read this.$refs.emergencyAccessSetup (or the Composition ref
emergencyAccessSetup) and if falsy or emergencyAccessSetup.hasValidationErrors
is true, do not call the child API (e.g., split()) or proceed with processing.
Ensure the early-return is placed before any invocation of EmergencyAccessSetup
methods (like split()) so keyboard/programmatic submits cannot bypass
validation.
♻️ Duplicate comments (1)
frontend/src/components/CreateVault.vue (1)

344-349: ⚠️ Potential issue | 🟠 Major

Don't fail open when emergency-access settings cannot be loaded.

Line 348 only logs the failure. If backend.settings.get() rejects, settings.value stays undefined, and validateVaultDetails() later treats that as “emergency access disabled”, letting vault creation skip the mandatory step. Keep the flow blocked until the settings load succeeds or the user can retry.

💡 Suggested change
 const settings = ref<SettingsDto>();
+const settingsLoadError = ref(false);

 async function loadDefaultEmergencyAccessSettings() {
   try {
     settings.value = await backend.settings.get();
+    settingsLoadError.value = false;
   } catch (error) {
     console.error('Loading emergency access settings failed:', error);
+    settings.value = undefined;
+    settingsLoadError.value = true;
   }
 }

 async function validateVaultDetails() {
   onCreateError.value = undefined;
+  if (settingsLoadError.value) {
+    onCreateError.value = new Error('Emergency access settings could not be loaded.');
+    return;
+  }
   if (!form.value?.checkValidity()) {
     onCreateError.value = new FormValidationFailedError();
     return;
   }

Also applies to: 411-424

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/CreateVault.vue` around lines 344 - 349, The
loadDefaultEmergencyAccessSettings function currently swallows
backend.settings.get() failures and leaves settings.value undefined (which
validateVaultDetails treats as “disabled”), so change the catch to block
progress: either rethrow the caught error after logging (so callers must
handle/fail the create flow) or set an explicit error flag/state (e.g.,
settingsLoadError = true and settings.value = null) that validateVaultDetails
checks and refuses to proceed; apply the same change to the other settings-load
block (the duplicate around the 411–424 area) and update validateVaultDetails to
treat an error/unknown settings state as blocking the vault creation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue`:
- Around line 115-116: Reset the visible error state at the start of the user
action handlers so old errors don't persist: clear onAddCouncilMemberError.value
at the top of the dialog submit/retry handlers (the function that calls
splitRecoveryKey()) and also at the start of splitRecoveryKey() itself, so each
attempt begins with a clean error state; locate references to
onAddCouncilMemberError, open, and splitRecoveryKey to add the clear before any
async calls or early returns.

---

Outside diff comments:
In `@frontend/src/components/CreateVault.vue`:
- Around line 109-140: The submit handler validateVaultEmergencyAccess() must
mirror the disabled-button guard by checking the EmergencyAccessSetup ref for
existence and for hasValidationErrors and return early if validation fails;
update validateVaultEmergencyAccess() to read this.$refs.emergencyAccessSetup
(or the Composition ref emergencyAccessSetup) and if falsy or
emergencyAccessSetup.hasValidationErrors is true, do not call the child API
(e.g., split()) or proceed with processing. Ensure the early-return is placed
before any invocation of EmergencyAccessSetup methods (like split()) so
keyboard/programmatic submits cannot bypass validation.

---

Duplicate comments:
In `@frontend/src/components/CreateVault.vue`:
- Around line 344-349: The loadDefaultEmergencyAccessSettings function currently
swallows backend.settings.get() failures and leaves settings.value undefined
(which validateVaultDetails treats as “disabled”), so change the catch to block
progress: either rethrow the caught error after logging (so callers must
handle/fail the create flow) or set an explicit error flag/state (e.g.,
settingsLoadError = true and settings.value = null) that validateVaultDetails
checks and refuses to proceed; apply the same change to the other settings-load
block (the duplicate around the 411–424 area) and update validateVaultDetails to
treat an error/unknown settings state as blocking the vault creation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44dfdf12-0d06-48ca-a39d-4d6edf5eeef8

📥 Commits

Reviewing files that changed from the base of the PR and between 63a8d6d and 1d09ccc.

📒 Files selected for processing (4)
  • frontend/src/components/CreateVault.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue
  • frontend/src/components/emergencyaccess/EmergencyScenarioVisualization.vue
  • frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue
  • frontend/src/components/emergencyaccess/EmergencyScenarioVisualization.vue

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue (1)

123-146: ⚠️ Potential issue | 🟡 Minor

Clear error state at the start of splitRecoveryKey().

The error from a previous attempt persists until the next error or dialog close. Clear onAddCouncilMemberError at the start so users don't see stale errors during retry.

🔧 Proposed fix
 async function splitRecoveryKey() {
+  onAddCouncilMemberError.value = undefined;
   if (!emergencyAccessSetup.value) {
     console.warn('EmergencyAccessSetup not yet mounted.');
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue`
around lines 123 - 146, The splitRecoveryKey function can surface a stale error
from a previous attempt; clear the error state at the start of splitRecoveryKey
by resetting onAddCouncilMemberError.value = null (or undefined) before any
async work. Update the splitRecoveryKey function (referencing
emergencyAccessSetup, props.vault, backend.vaults.createOrUpdateVault, and
emit('updated', ...)) to clear onAddCouncilMemberError immediately after the
emergencyAccessSetup check and before calling emergencyAccessSetup.value.split
so retries don't show stale errors.
frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue (1)

741-750: ⚠️ Potential issue | 🟡 Minor

Insufficient council members falls through to wrong error.

If councilMembers.value.length < process.details.newRequiredKeyShares for a COUNCIL_CHANGE process, the condition at line 741 is false, causing fallthrough to line 773's generic "unsupported process type" error. This makes diagnosing completion failures difficult.

🔧 Proposed fix
-    if (process.type === 'COUNCIL_CHANGE' && councilMembers.value.length >= process.details.newRequiredKeyShares) {
+    if (process.type === 'COUNCIL_CHANGE') {
+      if (councilMembers.value.length < process.details.newRequiredKeyShares) {
+        throw new Error(t('emergencyAccessDialog.error.insufficientCouncilMembers', [councilMembers.value.length, process.details.newRequiredKeyShares]));
+      }
       const keyShares = await EmergencyAccess.split(recoveredKeyBytes, process.details.newRequiredKeyShares, ...councilMembers.value);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue` around
lines 741 - 750, The code path for handling process.type === 'COUNCIL_CHANGE'
currently only executes when councilMembers.value.length >=
process.details.newRequiredKeyShares, so when there are too few councilMembers
it falls through to the generic "unsupported process type" error; add an
explicit guard that detects councilMembers.value.length <
process.details.newRequiredKeyShares when process.type === 'COUNCIL_CHANGE' and
surface a clear error (throw or return a rejected Promise / set a specific error
state) before the fallthrough. Locate the block referencing process.type,
councilMembers.value, process.details.newRequiredKeyShares,
EmergencyAccess.split and backend.vaults.createOrUpdateVault in
EmergencyAccessDialog.vue and implement the early check to produce a descriptive
error message (e.g., "insufficient council members for council change: need X,
have Y") instead of letting execution continue to the unsupported-type branch.
🧹 Nitpick comments (1)
frontend/src/components/CreateVault.vue (1)

462-468: Nullish coalescing is redundant but harmless.

The refs requiredEmergencyKeyShares and emergencyKeyShares are already initialized with 0 and {} respectively (lines 330-331), so the ?? 0 and ?? {} fallbacks will never be triggered. Consider removing them for clarity, but this is not a functional issue.

♻️ Optional cleanup
     await backend.vaults.createOrUpdateVault(
       vaultId, 
       vaultName.value, 
       false, 
-      requiredEmergencyKeyShares.value ?? 0, 
-      emergencyKeyShares.value ?? {}, 
+      requiredEmergencyKeyShares.value, 
+      emergencyKeyShares.value, 
       vaultDescription.value);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/CreateVault.vue` around lines 462 - 468, The call to
backend.vaults.createOrUpdateVault uses redundant nullish-coalescing on refs
that are already initialized; remove the "?? 0" after
requiredEmergencyKeyShares.value and the "?? {}" after emergencyKeyShares.value
and pass requiredEmergencyKeyShares.value and emergencyKeyShares.value directly
(keeping vaultId, vaultName.value, false, and vaultDescription.value as-is) so
the call uses the existing initialized defaults without extra fallbacks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue`:
- Around line 445-450: The code in EmergencyAccessDialog.vue reads
emergencyAccessSetup.value.requiredKeyShares (used to compute
sameRequiredKeyShares) but that property isn't exposed from the
EmergencyAccessSetup component, so it will be undefined; update
EmergencyAccessSetup.vue to expose the requiredKeyShares (via defineExpose or
computed prop) so emergencyAccessSetup.value.requiredKeyShares is available at
runtime and then the sameRequiredKeyShares comparison in the
processType==='COUNCIL_CHANGE' branch will work correctly.

In `@frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue`:
- Around line 110-114: The parent component (EmergencyAccessDialog.vue) tries to
read emergencyAccessSetup.value.requiredKeyShares but requiredKeyShares is not
included in the component's defineExpose call; update the defineExpose call in
EmergencyAccessSetup.vue to include requiredKeyShares (alongside split,
hasValidationErrors, emergencyCouncilMembers) so the ref exposes that property
to the parent, ensuring EmergencyAccessDialog can access
emergencyAccessSetup.value.requiredKeyShares.

---

Duplicate comments:
In `@frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue`:
- Around line 741-750: The code path for handling process.type ===
'COUNCIL_CHANGE' currently only executes when councilMembers.value.length >=
process.details.newRequiredKeyShares, so when there are too few councilMembers
it falls through to the generic "unsupported process type" error; add an
explicit guard that detects councilMembers.value.length <
process.details.newRequiredKeyShares when process.type === 'COUNCIL_CHANGE' and
surface a clear error (throw or return a rejected Promise / set a specific error
state) before the fallthrough. Locate the block referencing process.type,
councilMembers.value, process.details.newRequiredKeyShares,
EmergencyAccess.split and backend.vaults.createOrUpdateVault in
EmergencyAccessDialog.vue and implement the early check to produce a descriptive
error message (e.g., "insufficient council members for council change: need X,
have Y") instead of letting execution continue to the unsupported-type branch.

In `@frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue`:
- Around line 123-146: The splitRecoveryKey function can surface a stale error
from a previous attempt; clear the error state at the start of splitRecoveryKey
by resetting onAddCouncilMemberError.value = null (or undefined) before any
async work. Update the splitRecoveryKey function (referencing
emergencyAccessSetup, props.vault, backend.vaults.createOrUpdateVault, and
emit('updated', ...)) to clear onAddCouncilMemberError immediately after the
emergencyAccessSetup check and before calling emergencyAccessSetup.value.split
so retries don't show stale errors.

---

Nitpick comments:
In `@frontend/src/components/CreateVault.vue`:
- Around line 462-468: The call to backend.vaults.createOrUpdateVault uses
redundant nullish-coalescing on refs that are already initialized; remove the
"?? 0" after requiredEmergencyKeyShares.value and the "?? {}" after
emergencyKeyShares.value and pass requiredEmergencyKeyShares.value and
emergencyKeyShares.value directly (keeping vaultId, vaultName.value, false, and
vaultDescription.value as-is) so the call uses the existing initialized defaults
without extra fallbacks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67b2df51-8017-43e5-947d-ba66ded25c7b

📥 Commits

Reviewing files that changed from the base of the PR and between 1d09ccc and df73e11.

📒 Files selected for processing (5)
  • frontend/src/components/CreateVault.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessSetup.vue
  • frontend/src/components/emergencyaccess/EmergencyAccessVaultList.vue
  • frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue`:
- Line 148: The readonly EmergencyAccess summary is binding the current quorum
via recoveryProcess.requiredKeyShares but for COUNCIL_CHANGE it should show the
future quorum from recoveryProcess.details.newRequiredKeyShares; update the prop
passed to EmergencyAccessSetup (the :required-key-shares binding) to use
recoveryProcess.details.newRequiredKeyShares when recoveryProcess.type ===
'COUNCIL_CHANGE' (fall back to recoveryProcess.requiredKeyShares otherwise) so
the dialog shows the new council quorum; locate the EmergencyAccessSetup usage
and recoveryProcess handling to implement this conditional prop change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 914d44f0-2ca6-443b-b540-f0bbc50261e5

📥 Commits

Reviewing files that changed from the base of the PR and between df73e11 and 30486ad.

📒 Files selected for processing (2)
  • frontend/src/components/emergencyaccess/EmergencyAccessDialog.vue
  • frontend/src/components/emergencyaccess/GrantEmergencyAccessDialog.vue

@mindmonk
Copy link
Copy Markdown
Contributor

I noticed that the TrustDetails popover also inherits the opacity-60, which makes the popover itself semi-transparent and hard to read (see screenshot).

@overheadhunter I’ve already found a simple fix for this and would like to commit it, if that’s okay.

Bildschirmfoto 2026-03-25 um 10 55 06

@mindmonk
Copy link
Copy Markdown
Contributor

When starting a change council emergency access process, the "no redundancy" badge is currently shown even though the process cannot actually be started.

This badge should only be displayed if the defaults in the admin settings allow councils with no redundancy. My suggestion is to only show the "no redundancy" badge when the process is actually startable.
Bildschirmfoto 2026-03-25 um 12 05 08

@mindmonk
Copy link
Copy Markdown
Contributor

Bildschirmfoto 2026-03-31 um 16 55 08 The "New Council Members" field in the "Create Vault" flow remains empty, even when default council members are defined in the admin settings.

@overheadhunter
Copy link
Copy Markdown
Member Author

@mindmonk this problem was caused by 01ec6ba on develop and has now been fixed

Copy link
Copy Markdown
Contributor

@mindmonk mindmonk left a comment

Choose a reason for hiding this comment

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

approve for functional testing

@SailReal SailReal merged commit 19bf114 into develop Apr 1, 2026
8 checks passed
@SailReal SailReal deleted the feature/ea-cleanup branch April 1, 2026 11:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants