From a69c77c6ae1681089b52757831ffbc77c89396fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Mathurin?= Date: Thu, 4 Jun 2026 10:17:32 +0200 Subject: [PATCH 1/3] fix: Ensure previous button sets lower stage in graph --- panel/pipeline.py | 27 ++++++++++++---------- panel/tests/test_pipeline.py | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/panel/pipeline.py b/panel/pipeline.py index 4d2149eeab..98b9e825b4 100644 --- a/panel/pipeline.py +++ b/panel/pipeline.py @@ -359,21 +359,24 @@ def _prev_stage(self): def _update_button(self): stage, kwargs = self._stages[self._stage] - options = list(self._graph.get(self._stage, [])) + next_options = list(self._graph.get(self._stage, [])) next_param = kwargs.get('next_parameter', self.next_parameter) - option = getattr(self._state, next_param) if next_param and next_param in stage.param else None - if option is None: - option = options[0] if options else None - self.next_selector.options = options - self.next_selector.value = option - self.next_selector.disabled = not bool(options) - previous = [] + next_option = getattr(self._state, next_param) if next_param and next_param in stage.param else None + if next_option is None: + next_option = next_options[0] if next_options else None + self.next_selector.options = next_options + self.next_selector.value = next_option + self.next_selector.disabled = not bool(next_options) + previous_options = [] + previous_option = None for src, tgts in self._graph.items(): if self._stage in tgts: - previous.append(src) - self.prev_selector.options = previous - self.prev_selector.value = self._route[-1] if previous else None - self.prev_selector.disabled = not bool(previous) + previous_options.append(src) + if src in self._route: + previous_option = src + self.prev_selector.options = previous_options + self.prev_selector.value = previous_option + self.prev_selector.disabled = not bool(previous_options) # Disable previous button if self._prev_stage is None: diff --git a/panel/tests/test_pipeline.py b/panel/tests/test_pipeline.py index 04ba4e2112..b76ffe02a7 100644 --- a/panel/tests/test_pipeline.py +++ b/panel/tests/test_pipeline.py @@ -445,3 +445,46 @@ def test_pipeline_ready_parameter_preserved_on_back_navigation(): # The next button must be enabled immediately (not disabled) assert not pipeline.next_button.disabled + + +def test_previous_sets_lower_stage(): + """ + Non regression test for https://github.com/holoviz/panel/issues/5687 + + When navigating back from stage N to stage N-1, and then hitting previous once + again, make sure current state is stage N-2 and not stage N. + """ + pipeline = Pipeline() + + class Stage1(param.Parameterized): + + def panel(self): + return "Stage 1" + + class Stage2(param.Parameterized): + + def panel(self): + return "Stage 2" + + class Stage3(param.Parameterized): + + def panel(self): + return "Stage 3" + + pipeline.add_stage("Stage 1", Stage1) + pipeline.add_stage("Stage 2", Stage2) + pipeline.add_stage("Stage 3", Stage3) + + pipeline._next() + pipeline._next() + + assert pipeline._stage == "Stage 3" + assert pipeline._route == ["Stage 1", "Stage 2", "Stage 3"] + + pipeline._previous() + assert pipeline._stage == "Stage 2" + assert pipeline._route == ["Stage 1", "Stage 2"] + pipeline._previous() + + assert pipeline._stage == "Stage 1" + assert pipeline._route == ["Stage 1"] From ce0265ec7f59203a0ae450f6adde1a548208b834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Mathurin?= Date: Thu, 4 Jun 2026 16:19:01 +0200 Subject: [PATCH 2/3] refactor: use existing DummyStage in test --- panel/tests/test_pipeline.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/panel/tests/test_pipeline.py b/panel/tests/test_pipeline.py index b76ffe02a7..64741dc4af 100644 --- a/panel/tests/test_pipeline.py +++ b/panel/tests/test_pipeline.py @@ -456,24 +456,9 @@ def test_previous_sets_lower_stage(): """ pipeline = Pipeline() - class Stage1(param.Parameterized): - - def panel(self): - return "Stage 1" - - class Stage2(param.Parameterized): - - def panel(self): - return "Stage 2" - - class Stage3(param.Parameterized): - - def panel(self): - return "Stage 3" - - pipeline.add_stage("Stage 1", Stage1) - pipeline.add_stage("Stage 2", Stage2) - pipeline.add_stage("Stage 3", Stage3) + pipeline.add_stage("Stage 1", DummyStage()) + pipeline.add_stage("Stage 2", DummyStage()) + pipeline.add_stage("Stage 3", DummyStage()) pipeline._next() pipeline._next() From a6c73ea26389e61489e3bf8b1fc6759cf1a96872 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 5 Jun 2026 12:10:03 +0200 Subject: [PATCH 3/3] Apply suggestion from @philippjfr --- panel/tests/test_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panel/tests/test_pipeline.py b/panel/tests/test_pipeline.py index a230e5f4c3..bb5cb182e4 100644 --- a/panel/tests/test_pipeline.py +++ b/panel/tests/test_pipeline.py @@ -483,4 +483,4 @@ def test_previous_sets_lower_stage(): pipeline._previous() assert pipeline._stage == "Stage 1" - assert pipeline._route == ["Stage 1"] \ No newline at end of file + assert pipeline._route == ["Stage 1"]