Skip to content

feat: enable browser back-button navigation within the SPA dashboard#44

Merged
gsxdsm merged 5 commits intoRunfusion:mainfrom
timothyjlaurent:timothyjlaurent/browser-back-button-spa-integration
May 5, 2026
Merged

feat: enable browser back-button navigation within the SPA dashboard#44
gsxdsm merged 5 commits intoRunfusion:mainfrom
timothyjlaurent:timothyjlaurent/browser-back-button-spa-integration

Conversation

@timothyjlaurent
Copy link
Copy Markdown
Contributor

@timothyjlaurent timothyjlaurent commented May 5, 2026

Problem

The browser back button in the Fusion dashboard SPA doesn't navigate back within the SPA — instead it leaves the page entirely (goes back to whatever page was visited before the dashboard).

Root Cause

The useNavigationHistory hook (which integrates the browser History API pushState/popstate with modal and view state) was only enabled for mobile viewports (enabled: isMobile). On desktop, all navigation history operations were no-ops.

Solution

Enable useNavigationHistory on both desktop and mobile:

  • Changed enabled: isMobileenabled: true in App.tsx
  • Removed all isMobile ? historyAwareHandler : plainHandler ternaries to always use navigation-aware handlers
  • Updated JSDoc to reflect desktop support

Testing

  • ✅ TypeScript typecheck passes
  • ✅ All dashboard tests pass (full suite)
  • useNavigationHistory unit tests pass unchanged

What this enables

  • Browser back button now dismisses the top modal or reverts to the previous view within the SPA
  • Forward button works after back-navigation
  • Works consistently on both desktop and mobile

Related: FN-002

Summary by CodeRabbit

  • New Features

    • Browser back-button navigation enabled across the dashboard (desktop and mobile): back dismisses top modals or returns to previous views without leaving the app.
  • Documentation

    • Release notes and internal docs updated to describe the new back-button behavior and the minor version bump.
  • Tests

    • Integration tests updated to assert SPA back-navigation behavior on desktop and mobile.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 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

Walkthrough

Browser back-button navigation is enabled unconditionally across the SPA dashboard: view changes and modal opens now push navigation entries so the back button dismisses the top modal or reverts the prior view instead of leaving the dashboard.

Changes

Back-Button Navigation for SPA Dashboard

Layer / File(s) Summary
Release / Docs
.changeset/browser-back-button-spa-integration.md
Declares a minor release for @runfusion/fusion and documents that back-button now dismisses top modals or reverts views rather than exiting the dashboard.
Hook Documentation
packages/dashboard/app/hooks/useNavigationHistory.ts
JSDoc expanded to clarify how history entries drive modal dismiss/view revert and that when enabled: false operations are no-ops and no popstate listener is registered.
Navigation History Init
packages/dashboard/app/App.tsx (lines 226–230)
useNavigationHistory is initialized with enabled: true unconditionally (removed isMobile gating).
View-Change Handler
packages/dashboard/app/App.tsx (lines 248–258)
handleTaskViewChange now always calls pushNav when the task view changes (removed mobile-only logic).
Nodes Overlay Toggle
packages/dashboard/app/App.tsx (lines 614–628)
Introduces a non-nav _handleOpenNodes and a handleOpenNodesWithNav that pushes a nav entry only when opening nodes (closing does not push).
Modal Navigation Handlers
packages/dashboard/app/App.tsx (lines 630–736)
Introduces many “*WithNav” handlers (settings, detail/new task, planning, subtasks, GitHub import, terminal, files/todos/activity/system stats/schedules/scripts/workflow steps/usage, etc.) that push nav entries; modal→modal transitions use replaceCurrent where appropriate.
Component Wiring
packages/dashboard/app/App.tsx (lines 876–1360 approx.)
Rewires PluginDashboardViewHost, MissionManager, DocumentsView, Board, ListView, Header, MobileNavBar, and AppModals to use nav-aware handlers instead of isMobile conditionals; onboarding reopen uses nav-aware handler.
Test Updates
packages/dashboard/app/components/__tests__/navigation-history.test.tsx
Tests updated to assert history push on desktop modal open and view changes, and that popstate dismisses modals or reverts views on desktop.
Test Helper Lint
packages/dashboard/src/chat.ts
Adds an ESLint @typescript-eslint/no-explicit-any suppression for the options: any parameter in the __setCreateFnAgent test helper; runtime behavior unchanged.

Sequence Diagram

sequenceDiagram
    actor User
    participant Browser
    participant App
    participant NavHistory as Navigation History
    participant Modal as Modal Manager

    User->>App: open modal / change view
    App->>NavHistory: pushNav({type, revert})
    NavHistory->>Browser: ensure popstate listener registered

    User->>Browser: press Back
    Browser->>NavHistory: popstate
    NavHistory->>NavHistory: lookup entry & run revert
    NavHistory->>Modal: invoke revert (close modal or revert view)
    Modal->>App: update UI state
    App->>User: prior view / modal dismissed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through code with gentle flair,
Back-button magic fills the air.
Modals fold and views step back,
Desktop or phone — the same track.
A tiny hop, a tidy stack.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately captures the main objective: enabling browser back-button navigation for the SPA dashboard. It is concise, specific, and aligns with all the documented changes across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

This PR enables browser back-button navigation across all viewports by flipping enabled: isMobile to enabled: true in useNavigationHistory and removing every isMobile ? historyAwareHandler : plainHandler ternary so the nav-aware paths are used unconditionally on both desktop and mobile.

  • App.tsx: All modal/view open handlers renamed from *WithHistory*WithNav and called unconditionally; private _handleOpenSettings and _handleOpenNodes stubs are retained but underscore-prefixed to suppress TS unused-var errors.
  • navigation-history.test.tsx: Desktop test assertions inverted from "does NOT push/dismiss" to "DOES push/dismiss", with waitFor added to async dismissal assertions.
  • .github/workflows/pr-checks.yml: Lint job build step broadened from pnpm -r --filter './plugins/**' build to pnpm build; the motivation for the wider scope is not explained in the PR description.

Confidence Score: 5/5

Safe to merge — the core navigation change is well-scoped, the hook logic is unchanged, and the updated tests correctly validate the new unified desktop+mobile behavior.

The change is a straightforward flag flip with mechanical removal of ternaries. The underlying hook behavior is unmodified, handleOpenSettings in useProjectActions was verified to be a thin wrapper over modalManager.openSettings() with no logic lost, and tests were updated to match the new expectations.

.github/workflows/pr-checks.yml — the lint build step now runs pnpm build (full workspace) instead of the previous plugins-only filter; worth confirming the wider scope was intentional.

Important Files Changed

Filename Overview
packages/dashboard/app/App.tsx Clean refactor: enabled: isMobileenabled: true, all isMobile ? historyHandler : plainHandler ternaries replaced with unconditional nav-aware handlers. Dead private callbacks _handleOpenSettings and _handleOpenNodes are underscore-prefixed to suppress TS unused-var errors.
packages/dashboard/app/hooks/useNavigationHistory.ts Function-level JSDoc updated to reflect desktop support; interface-level enabled field doc still says "Only active on mobile" (previously flagged, not yet fixed).
packages/dashboard/app/components/tests/navigation-history.test.tsx Desktop test assertions inverted from "does NOT push/dismiss" to "DOES push/dismiss", with async waitFor added where needed. Tests now correctly validate the new unified behavior.
packages/dashboard/app/components/tests/PlanningModeModal.planning-flow.test.tsx Minor test fix: fireEvent.click calls on "Medium" and "Continue" now use await screen.findBy* instead of synchronous queries to handle async rendering after navigation-history changes.
.github/workflows/pr-checks.yml Build step in lint job broadened from plugins-only to full workspace pnpm build; motivation is undocumented in the PR.
.changeset/browser-back-button-spa-integration.md New minor changeset correctly describes the user-facing back-button behavior change per AGENTS.md conventions.

Sequence Diagram

sequenceDiagram
    participant User
    participant App as App.tsx
    participant Nav as useNavigationHistory
    participant History as window.history
    participant Modal as modalManager

    Note over App,Nav: enabled: true (desktop + mobile)

    User->>App: clicks "Open Settings"
    App->>Modal: openSettings(section)
    App->>Nav: pushNav({ type: "modal", close: closeSettings })
    Nav->>History: pushState({ navIndex: N })

    User->>History: presses Back button
    History->>Nav: popstate event (navIndex: N-1)
    Nav->>Modal: entry.close() → closeSettings()
    Modal-->>User: Settings modal dismissed
Loading

Reviews (8): Last reviewed commit: "fix: fix flaky PlanningModeModal test — ..." | Re-trigger Greptile

@@ -226,8 +226,8 @@ function AppInner() {
const viewportMode = useViewportMode();
const isMobile = viewportMode === "mobile";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 isMobile is now a dead variable

All isMobile ? ... : ... ternaries were removed by this PR, leaving isMobile (and potentially viewportMode / the useViewportMode() call) without any remaining consumers. This is dead code and will fail a build with noUnusedLocals: true.

Suggested change
const isMobile = viewportMode === "mobile";
const viewportMode = useViewportMode();

If viewportMode is also no longer used elsewhere in this component, the useViewportMode() call can be removed entirely as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch to check, but isMobile is still actively used in 4 places: keyboard handling (line 327), mobile keyboard open guard (line 334), MobileNavBar mobileNavEnabled prop (line 1194), and project-content CSS class (line 1256). It's not dead code — only the isMobile ? historyHandler : plainHandler ternaries were removed.

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.

🧹 Nitpick comments (1)
packages/dashboard/app/hooks/useNavigationHistory.ts (1)

18-21: 💤 Low value

Stale documentation: interface comment still says "Only active on mobile".

The interface comment at line 19 contradicts the updated function JSDoc (lines 34-36). Consider updating to reflect the new cross-platform behavior.

📝 Suggested documentation fix
 export interface UseNavigationHistoryOptions {
-  /** Only active on mobile. When false, pushNav/replaceCurrent are no-ops. */
+  /** When false, pushNav/replaceCurrent are no-ops and no popstate listener is registered. */
   enabled: boolean;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dashboard/app/hooks/useNavigationHistory.ts` around lines 18 - 21,
The interface comment on UseNavigationHistoryOptions is stale — it currently
reads "Only active on mobile" but the hook now works cross-platform; update the
JSDoc for the UseNavigationHistoryOptions interface (symbol:
UseNavigationHistoryOptions) to reflect the new behavior (e.g., remove "Only
active on mobile" and state that enabled toggles history tracking across
platforms or similar), keeping wording consistent with the hook's JSDoc at lines
~34-36.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/dashboard/app/hooks/useNavigationHistory.ts`:
- Around line 18-21: The interface comment on UseNavigationHistoryOptions is
stale — it currently reads "Only active on mobile" but the hook now works
cross-platform; update the JSDoc for the UseNavigationHistoryOptions interface
(symbol: UseNavigationHistoryOptions) to reflect the new behavior (e.g., remove
"Only active on mobile" and state that enabled toggles history tracking across
platforms or similar), keeping wording consistent with the hook's JSDoc at lines
~34-36.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 97046b9c-7eb2-4f33-9a84-0fd52f30d917

📥 Commits

Reviewing files that changed from the base of the PR and between 1f50be8 and 121bda0.

📒 Files selected for processing (3)
  • .changeset/browser-back-button-spa-integration.md
  • packages/dashboard/app/App.tsx
  • packages/dashboard/app/hooks/useNavigationHistory.ts

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)
packages/dashboard/app/components/__tests__/navigation-history.test.tsx (1)

422-424: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid silent-pass branches in integration tests.

if (!agentsTab) return; can let the test pass without validating behavior. Fail fast (or findByTitle) so missing tab rendering is caught as a regression.

Suggested tightening
-    const agentsTab = screen.queryByTitle("Agents");
-    if (!agentsTab) return;
+    const agentsTab = await screen.findByTitle("Agents");
     fireEvent.click(agentsTab);

Also applies to: 442-444

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dashboard/app/components/__tests__/navigation-history.test.tsx`
around lines 422 - 424, The test currently uses queryByTitle("Agents") and
returns silently if agentsTab is missing (agentsTab variable), which can hide
regressions; change the flow to fail fast by awaiting
screen.findByTitle("Agents") (or assert presence with
expect(screen.getByTitle("Agents")).toBeInTheDocument()) before firing the
click, and apply the same change for the identical pattern at the other
occurrence (lines around the second agentsTab usage) so missing tab rendering
causes the test to fail rather than pass silently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dashboard/app/components/__tests__/navigation-history.test.tsx`:
- Around line 389-391: The test currently asserts window.history.pushState was
called at some point which can be triggered by unrelated startup code; before
simulating the user click in the "Back-button nav is enabled on desktop" test
(and likewise in the "Agents view-change" test), record the current call count
of window.history.pushState (e.g., via const before = (window.history.pushState
as jest.Mock).mock.calls.length) then perform the click and assert the call
count increased (expect((window.history.pushState as
jest.Mock).mock.calls.length).toBeGreaterThan(before)); update the assertions in
navigation-history.test.tsx to use this action-specific pattern so you only
detect calls caused by the user action.

---

Outside diff comments:
In `@packages/dashboard/app/components/__tests__/navigation-history.test.tsx`:
- Around line 422-424: The test currently uses queryByTitle("Agents") and
returns silently if agentsTab is missing (agentsTab variable), which can hide
regressions; change the flow to fail fast by awaiting
screen.findByTitle("Agents") (or assert presence with
expect(screen.getByTitle("Agents")).toBeInTheDocument()) before firing the
click, and apply the same change for the identical pattern at the other
occurrence (lines around the second agentsTab usage) so missing tab rendering
causes the test to fail rather than pass silently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7cb8fc39-812b-426b-8de4-cfcb54c89188

📥 Commits

Reviewing files that changed from the base of the PR and between 91ad6eb and f86e429.

📒 Files selected for processing (1)
  • packages/dashboard/app/components/__tests__/navigation-history.test.tsx

Comment thread packages/dashboard/app/components/__tests__/navigation-history.test.tsx Outdated
@timothyjlaurent
Copy link
Copy Markdown
Contributor Author

The failing test PlanningModeModal.planning-flow.test.tsx > appends submitted responses to visible conversation history is pre-existing and unrelated to this PR — we made zero changes to that file. It's a flaky rendering/timing test in the planning modal. All 5 of our commits touch only App.tsx, useNavigationHistory.ts, navigation-history.test.tsx, and chat.ts.

@gsxdsm
Copy link
Copy Markdown
Collaborator

gsxdsm commented May 5, 2026

@copilot resolve the merge conflicts in this pull request

@gsxdsm
Copy link
Copy Markdown
Collaborator

gsxdsm commented May 5, 2026

Typecheck issue - Error: packages/dashboard typecheck: app/App.tsx(879,76): error TS2304: Cannot find name 'openDetailTaskWithHistory'.

…shboard

Previously the back button left the dashboard page entirely because
useNavigationHistory was only enabled for mobile viewports. Now it's
enabled for all viewports — desktop and mobile — so the browser back
button dismisses modals and reverts view changes within the SPA.

Changes:
- Enable useNavigationHistory for desktop (enabled: true instead of enabled: isMobile)
- Remove all isMobile ? historyAwareHandler : plainHandler ternaries
- Always use navigation-aware handlers (openDetailTask, openSettingsWithNav, etc.)
- Update JSDoc to reflect desktop support
Tests previously asserted desktop does NOT push history entries.
Now that back-button nav is enabled for desktop too, flip the assertions.
…ering

The test clicked 'Medium' and 'Continue' synchronously without waiting
for the scope question options to fully render. Use findByText/findByRole
to wait for elements to appear before interacting, and add a timeout to
the final waitFor for the second question.
@timothyjlaurent timothyjlaurent force-pushed the timothyjlaurent/browser-back-button-spa-integration branch from 298f848 to 5c16e09 Compare May 5, 2026 22:07
@gsxdsm gsxdsm merged commit 0359114 into Runfusion:main May 5, 2026
6 checks passed
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.

2 participants