From 5c7dcd179abfdce3cfc47b71a986700a4826ff71 Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Mon, 6 Apr 2026 22:34:08 +0530 Subject: [PATCH 1/9] fix back navigation --- src/seedsigner/views/seed_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index e938da49..fe29568e 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -230,7 +230,7 @@ class SeedMnemonicEntryView(View): return Destination(BackStackView) else: self.controller.storage.discard_pending_mnemonic() - return Destination(MainMenuView) + return Destination(BackStackView) # ret will be our new mnemonic word self.controller.storage.update_pending_mnemonic(ret, self.cur_word_index) @@ -332,6 +332,9 @@ class SeedFinalizeView(View): button_data=button_data, ) + if selected_menu_num == RET_CODE__BACK_BUTTON: + return Destination(BackStackView) + if button_data[selected_menu_num] == self.FINALIZE: seed_num = self.controller.storage.finalize_pending_seed() return Destination(SeedOptionsView, view_args={"seed_num": seed_num}, clear_history=True) @@ -339,9 +342,6 @@ class SeedFinalizeView(View): elif button_data[selected_menu_num] == self.PASSPHRASE: return Destination(SeedAddPassphraseView) - elif selected_menu_num == RET_CODE__BACK_BUTTON: - return Destination(BackStackView) - class SeedAddPassphraseView(View): From 3f2344af60e1005bcd95f1f7b32c96fefb099964 Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Mon, 6 Apr 2026 22:34:50 +0530 Subject: [PATCH 2/9] add tests --- tests/test_flows_seed.py | 104 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index e5f2c346..6db95f3d 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -495,6 +495,110 @@ class TestSeedFlows(FlowTest): +class TestSeedEntryBackFlows(FlowTest): + """ + Tests for every BACK exit scenario from SeedMnemonicEntryView and related views. + + A naive BackStackView swap can leave resume_main_flow dangling, causing + auto-redirects on stale flow state. These tests verify that BACK navigation + returns to the correct parent view AND that no flow state leaks. + """ + + def test_back_from_seed_entry_12_word(self): + """ + Seeds Menu → Load a Seed → Enter 12-word → BACK on first word → + should return to LoadSeedView, NOT MainMenuView. + """ + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, is_redirect=True), # No seeds loaded; auto-redirects to LoadSeedView + FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_12WORD), + FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(seed_views.LoadSeedView), # Should land here, NOT MainMenuView + ]) + + + def test_back_from_seed_entry_24_word(self): + """ + Seeds Menu → Load a Seed → Enter 24-word → BACK on first word → + should return to LoadSeedView, NOT MainMenuView. + """ + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, is_redirect=True), # No seeds loaded; auto-redirects to LoadSeedView + FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_24WORD), + FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(seed_views.LoadSeedView), # Should land here, NOT MainMenuView + ]) + + + def test_back_from_seed_entry_mid_word(self): + """ + Pressing BACK from a middle word (eg. word #2) should return to the + previous SeedMnemonicEntryView (eg. word #1) via the back stack. + """ + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, is_redirect=True), + FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_12WORD), + FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value="abandon"), # word #1 + FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), # BACK from word #2 + FlowStep(seed_views.SeedMnemonicEntryView), # Returns to word #1 + ]) + + + def test_back_from_seed_entry_via_seed_select(self): + """ + When entering a seed via SeedSelectSeedView (e.g. during sign message flow), + pressing BACK on the first word should return to SeedSelectSeedView, NOT + MainMenuView. Crucially, resume_main_flow must remain valid since the user + is still within that flow. + """ + from seedsigner.controller import Controller + from seedsigner.models.settings import SettingsConstants + + self.settings.set_value(SettingsConstants.SETTING__MESSAGE_SIGNING, SettingsConstants.OPTION__ENABLED) + + def load_signmessage_into_decoder(view): + view.decoder.add_data("signmessage m/84h/0h/0h/0/0 ascii:test message") + + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_signmessage_into_decoder), + FlowStep(seed_views.SeedSignMessageStartView, is_redirect=True), + FlowStep(seed_views.SeedSelectSeedView, button_data_selection=seed_views.SeedSelectSeedView.TYPE_12WORD), + FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), # BACK on first word + FlowStep(seed_views.SeedSelectSeedView), # Should return here, in the sign message flow + ]) + + # Verify resume_main_flow is still set — user is still in the sign message flow + assert self.controller.resume_main_flow == Controller.FLOW__SIGN_MESSAGE + + + def test_back_from_seed_finalize(self): + """ + Pressing BACK from SeedFinalizeView should return to the last word + entry view via the back stack. + """ + mnemonic = "tone flat shed cool census soul paddle boy flight fantasy stem social".split() + sequence = [ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, is_redirect=True), + FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_12WORD), + ] + + for word in mnemonic: + sequence.append(FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=word)) + + sequence += [ + FlowStep(seed_views.SeedFinalizeView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(seed_views.SeedMnemonicEntryView), # Returns to last word entry + ] + + self.run_sequence(sequence) + + + class TestMessageSigningFlows(FlowTest): MAINNET_DERIVATION_PATH = "m/84h/0h/0h/0/0" TESTNET_DERIVATION_PATH = "m/84h/1h/0h/0/0" From 804dad9870b892fbbc39b55ca35026da7ac2cb8d Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Fri, 10 Apr 2026 00:04:27 +0530 Subject: [PATCH 3/9] remove test --- tests/test_flows_seed.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 6db95f3d..03dfc882 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -575,28 +575,6 @@ class TestSeedEntryBackFlows(FlowTest): assert self.controller.resume_main_flow == Controller.FLOW__SIGN_MESSAGE - def test_back_from_seed_finalize(self): - """ - Pressing BACK from SeedFinalizeView should return to the last word - entry view via the back stack. - """ - mnemonic = "tone flat shed cool census soul paddle boy flight fantasy stem social".split() - sequence = [ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), - FlowStep(seed_views.SeedsMenuView, is_redirect=True), - FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_12WORD), - ] - - for word in mnemonic: - sequence.append(FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=word)) - - sequence += [ - FlowStep(seed_views.SeedFinalizeView, screen_return_value=RET_CODE__BACK_BUTTON), - FlowStep(seed_views.SeedMnemonicEntryView), # Returns to last word entry - ] - - self.run_sequence(sequence) - class TestMessageSigningFlows(FlowTest): From ef7d3716d17a18caf10de4763c54109998750fbe Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Fri, 10 Apr 2026 00:05:05 +0530 Subject: [PATCH 4/9] update conditional branch --- src/seedsigner/views/seed_views.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index fe29568e..a2761f99 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -226,11 +226,10 @@ class SeedMnemonicEntryView(View): ) if ret == RET_CODE__BACK_BUTTON: - if self.cur_word_index > 0: - return Destination(BackStackView) - else: + # Only need to discard when exiting from the very first word + if self.cur_word_index == 0: self.controller.storage.discard_pending_mnemonic() - return Destination(BackStackView) + return Destination(BackStackView) # ret will be our new mnemonic word self.controller.storage.update_pending_mnemonic(ret, self.cur_word_index) @@ -332,9 +331,6 @@ class SeedFinalizeView(View): button_data=button_data, ) - if selected_menu_num == RET_CODE__BACK_BUTTON: - return Destination(BackStackView) - if button_data[selected_menu_num] == self.FINALIZE: seed_num = self.controller.storage.finalize_pending_seed() return Destination(SeedOptionsView, view_args={"seed_num": seed_num}, clear_history=True) From d0b729a52e558e006ef6b7d5f7efba2f80514e80 Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Mon, 1 Jun 2026 11:56:57 +0530 Subject: [PATCH 5/9] added comment explaining changes --- src/seedsigner/views/seed_views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index a2761f99..53370f0c 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -226,7 +226,10 @@ class SeedMnemonicEntryView(View): ) if ret == RET_CODE__BACK_BUTTON: - # Only need to discard when exiting from the very first word + # RET_CODE__BACK_BUTTON can happen in two ways here: + # 1. Backing out of the very first word completely aborts the mnemonic entry. + # 2. Backing out of a subsequent word just returns to the previous word. + # In both cases we return to BackStackView, but for case #1 we must also discard. if self.cur_word_index == 0: self.controller.storage.discard_pending_mnemonic() return Destination(BackStackView) From 60cb1376c05845bcc3490c6d47fb9139a5175439 Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Mon, 1 Jun 2026 11:57:54 +0530 Subject: [PATCH 6/9] refactor 12/24 word tests into a single test --- tests/test_flows_seed.py | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 03dfc882..601e7a44 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -504,32 +504,20 @@ class TestSeedEntryBackFlows(FlowTest): returns to the correct parent view AND that no flow state leaks. """ - def test_back_from_seed_entry_12_word(self): + def test_back_from_seed_entry_first_word(self): """ - Seeds Menu → Load a Seed → Enter 12-word → BACK on first word → + Seeds Menu → Load a Seed → Enter 12/24-word → BACK on first word → should return to LoadSeedView, NOT MainMenuView. """ - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), - FlowStep(seed_views.SeedsMenuView, is_redirect=True), # No seeds loaded; auto-redirects to LoadSeedView - FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_12WORD), - FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), - FlowStep(seed_views.LoadSeedView), # Should land here, NOT MainMenuView - ]) - - - def test_back_from_seed_entry_24_word(self): - """ - Seeds Menu → Load a Seed → Enter 24-word → BACK on first word → - should return to LoadSeedView, NOT MainMenuView. - """ - self.run_sequence([ - FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), - FlowStep(seed_views.SeedsMenuView, is_redirect=True), # No seeds loaded; auto-redirects to LoadSeedView - FlowStep(seed_views.LoadSeedView, button_data_selection=seed_views.LoadSeedView.TYPE_24WORD), - FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), - FlowStep(seed_views.LoadSeedView), # Should land here, NOT MainMenuView - ]) + for seed_type in [seed_views.LoadSeedView.TYPE_12WORD, seed_views.LoadSeedView.TYPE_24WORD]: + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SEEDS), + FlowStep(seed_views.SeedsMenuView, is_redirect=True), # No seeds loaded; auto-redirects to LoadSeedView + FlowStep(seed_views.LoadSeedView, button_data_selection=seed_type), + FlowStep(seed_views.SeedMnemonicEntryView, screen_return_value=RET_CODE__BACK_BUTTON), + FlowStep(seed_views.LoadSeedView), # Should land here, NOT MainMenuView + ]) + BaseTest.reset_controller() def test_back_from_seed_entry_mid_word(self): From 94485e525ce9156ddbf8f50e30d078faaf21b656 Mon Sep 17 00:00:00 2001 From: Kshitij <162005485+PROWLERx15@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:19:24 +0530 Subject: [PATCH 7/9] update comment Co-authored-by: kdmukai <934746+kdmukai@users.noreply.github.com> --- src/seedsigner/views/seed_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 53370f0c..cb2032ed 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -226,10 +226,10 @@ class SeedMnemonicEntryView(View): ) if ret == RET_CODE__BACK_BUTTON: - # RET_CODE__BACK_BUTTON can happen in two ways here: - # 1. Backing out of the very first word completely aborts the mnemonic entry. - # 2. Backing out of a subsequent word just returns to the previous word. - # In both cases we return to BackStackView, but for case #1 we must also discard. + # This handles two possible scenarios: + # 1. Backing out of the first word cancels the mnemonic entry process; + return to whichever `View` routed us here initially. + # 2. Backing out of the current word returns to the previous word. if self.cur_word_index == 0: self.controller.storage.discard_pending_mnemonic() return Destination(BackStackView) From cf37ac2a8e24046b4f170d1266cd53131f492a8e Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Tue, 2 Jun 2026 18:29:51 +0530 Subject: [PATCH 8/9] fix comment --- src/seedsigner/views/seed_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index cb2032ed..65830d37 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -227,8 +227,8 @@ class SeedMnemonicEntryView(View): if ret == RET_CODE__BACK_BUTTON: # This handles two possible scenarios: - # 1. Backing out of the first word cancels the mnemonic entry process; - return to whichever `View` routed us here initially. + # 1. Backing out of the first word cancels the mnemonic entry process; + # return to whichever `View` routed us here initially. # 2. Backing out of the current word returns to the previous word. if self.cur_word_index == 0: self.controller.storage.discard_pending_mnemonic() From 6ddc7a99c09bbdb1e055aa5a1435fc734db9d366 Mon Sep 17 00:00:00 2001 From: PROWLERx15 Date: Tue, 2 Jun 2026 19:07:51 +0530 Subject: [PATCH 9/9] changessss --- tests/test_flows_seed.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 601e7a44..578f0094 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -506,8 +506,8 @@ class TestSeedEntryBackFlows(FlowTest): def test_back_from_seed_entry_first_word(self): """ - Seeds Menu → Load a Seed → Enter 12/24-word → BACK on first word → - should return to LoadSeedView, NOT MainMenuView. + Pressing BACK on the first word of mnemonic entry should return to + the View that initiated the mnemonic entry process. """ for seed_type in [seed_views.LoadSeedView.TYPE_12WORD, seed_views.LoadSeedView.TYPE_24WORD]: self.run_sequence([ @@ -534,13 +534,16 @@ class TestSeedEntryBackFlows(FlowTest): FlowStep(seed_views.SeedMnemonicEntryView), # Returns to word #1 ]) + # Verify we're back on word #1: word at index 0 should still be set + # from the previous entry, while word at index 1 should be unset. + assert self.controller.storage.get_pending_mnemonic_word(0) == "abandon" + assert self.controller.storage.get_pending_mnemonic_word(1) is None + def test_back_from_seed_entry_via_seed_select(self): """ - When entering a seed via SeedSelectSeedView (e.g. during sign message flow), - pressing BACK on the first word should return to SeedSelectSeedView, NOT - MainMenuView. Crucially, resume_main_flow must remain valid since the user - is still within that flow. + Backing out of mnemonic entry during an active flow must preserve + `resume_main_flow` so the user remains within that flow. """ from seedsigner.controller import Controller from seedsigner.models.settings import SettingsConstants