Tea journaling on ATProto (alpha)
oolong docs back-button-implementation.md
7.0 kB

Back Button Implementation #

Overview #

Implemented a smart back button feature that allows users to navigate back to their previous page across the Arabica application. The solution uses a hybrid approach combining JavaScript's history.back() with intelligent fallbacks.

Approach Chosen: Hybrid JavaScript History with Smart Fallbacks #

Why This Approach? #

  1. Best User Experience: Uses browser history when available, preserving scroll position and form state
  2. Handles Edge Cases: Falls back gracefully for direct links, external referrers, and bookmarks
  3. Simple Implementation: No server-side session tracking needed
  4. HTMX Compatible: Works seamlessly with HTMX navigation and partial page updates

How It Works #

The implementation consists of:

  1. JavaScript Module (static/js/back-button.js):

    • Detects if the user came from within the app (same-origin referrer)
    • Uses history.back() for internal navigation (preserves history stack)
    • Falls back to a specified URL for external/direct navigation
    • Automatically re-initializes after HTMX content swaps
  2. HTML Attributes:

    • data-back-button: Marks an element as a back button
    • data-fallback: Specifies the fallback URL (default: /brews)
  3. Visual Design:

    • SVG arrow icon for clear affordance
    • Consistent styling matching the app's brown theme
    • Hover states for better interactivity

Implementation Details #

JavaScript Logic #

function handleBackNavigation(button) {
    const fallbackUrl = button.getAttribute('data-fallback') || '/brews';
    const referrer = document.referrer;
    
    // Check if referrer is from same origin
    const hasSameOriginReferrer = referrer && 
                                   referrer.startsWith(window.location.origin) &&
                                   referrer !== currentUrl;

    if (hasSameOriginReferrer) {
        window.history.back();  // Use browser history
    } else {
        window.location.href = fallbackUrl;  // Use fallback
    }
}

Edge Cases Handled #

  1. Direct Links (e.g., bookmarked URL):

    • Referrer: empty or external
    • Behavior: Navigate to fallback URL
  2. External Referrers (e.g., from social media):

    • Referrer: different origin
    • Behavior: Navigate to fallback URL
  3. Internal Navigation:

    • Referrer: same origin
    • Behavior: Use history.back() (preserves state)
  4. HTMX Partial Updates:

    • Automatically reinitializes buttons after HTMX swaps
    • Ensures back buttons in dynamically loaded content work
  5. Page Refresh:

    • Referrer: same as current URL
    • Behavior: Navigate to fallback URL (prevents staying on same page)

Files Modified #

New Files #

  1. static/js/back-button.js
    • Core back button logic
    • Initialization and event handling
    • HTMX integration

Modified Templates #

  1. templates/layout.tmpl

    • Added back-button.js script reference
  2. templates/brew_view.tmpl

    • Replaced static "Back to Brews" link with smart back button
    • Fallback: /brews
  3. templates/brew_form.tmpl

    • Added back button in header (for both new and edit modes)
    • Fallback: /brews
  4. templates/about.tmpl

    • Added back button in header
    • Fallback: / (home page)
  5. templates/terms.tmpl

    • Added back button in header
    • Fallback: / (home page)
  6. templates/manage.tmpl

    • Added back button in header
    • Fallback: /brews

Usage Examples #

Basic Back Button #

<button
    data-back-button
    data-fallback="/brews"
    class="...">
    Back
</button>

With Custom Fallback #

<button
    data-back-button
    data-fallback="/profile"
    class="...">
    Back to Profile
</button>

With Icon (as implemented) #

<button
    data-back-button
    data-fallback="/brews"
    class="inline-flex items-center text-brown-700 hover:text-brown-900 font-medium transition-colors cursor-pointer">
    <svg class="w-5 h-5" ...>
        <path d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
    </svg>
</button>

Example 1: Normal Flow #

  1. User visits / (home)
  2. Clicks "View All Brews" → /brews
  3. Clicks on a brew → /brews/abc123
  4. Clicks back button → Returns to /brews (via history.back())
  1. User opens bookmark directly to /brews/abc123
  2. Clicks back button → Navigates to /brews (fallback)

Example 3: External Referrer #

  1. User clicks link from Twitter to /brews/abc123
  2. Clicks back button → Navigates to /brews (fallback, not back to Twitter)

Example 4: Profile to Brew #

  1. User visits /profile/@alice.bsky.social
  2. Clicks on a brew → /brews/abc123
  3. Clicks back button → Returns to /profile/@alice.bsky.social

Limitations #

  1. No History Stack Detection:

    • Cannot reliably detect if history stack is empty
    • Uses referrer as a proxy, which is a reasonable heuristic
  2. Referrer Privacy:

    • Some browsers/users may disable referrer headers
    • Falls back to default URL in these cases (safe behavior)
  3. Cross-Origin Navigation:

    • Intentionally doesn't go back to external sites
    • This is a feature, not a bug (keeps users in the app)
  4. No History Length Check:

    • window.history.length is unreliable across browsers
    • Our referrer-based approach is more predictable

Future Enhancements (Optional) #

  1. Session Storage Tracking:

    • Could track navigation history in sessionStorage
    • Would allow more sophisticated back button logic
    • Trade-off: added complexity vs. marginal benefit
  2. Contextual Fallbacks:

    • Could pass context-specific fallbacks from server
    • Example: brew detail could remember which list it came from
    • Trade-off: requires server-side state or URL params
  3. Breadcrumb Integration:

    • Could display breadcrumbs alongside back button
    • Better for complex navigation hierarchies
    • Trade-off: more UI complexity

Testing Recommendations #

Manual testing scenarios:

  1. ✅ Navigate from home → brews → brew detail → back (should use history)
  2. ✅ Open brew detail via bookmark → back (should go to fallback)
  3. ✅ Navigate from feed → brew detail → back (should return to feed)
  4. ✅ Navigate from profile → brew detail → back (should return to profile)
  5. ✅ Open about page → back (should go to home)
  6. ✅ Edit brew form → back (should return to previous page)

Conclusion #

The implemented solution provides an excellent balance of:

  • User Experience: Preserves browser history when possible
  • Reliability: Always provides a sensible fallback
  • Simplicity: No server-side complexity or session tracking
  • Maintainability: Single JavaScript module, easy to understand
  • Compatibility: Works with HTMX, Alpine.js, and standard navigation

The approach handles all realistic edge cases while keeping the implementation straightforward and performant.