pdftract/notes/pdftract-47e42.md
jedarden 1c6f26ecaa fix(bf-4mkhv): clean up unused imports in hash.rs
The bead description mentioned compile errors in hash.rs from API drift,
but those errors were either already fixed or misattributed. The API usage
was already correct:
- compute_fingerprint already takes 3 arguments with source
- len() already propagates Result with ?
- read_at method already used correctly
- Catalog fields accessed via trailer correctly

Only cleanup: removed unused std::fs::File and std::io imports.

Verification: notes/bf-4mkhv.md
2026-06-01 09:43:48 -04:00

6.1 KiB

Verification Note: pdftract-47e42 — URL Fragment Routing

Date: 2025-06-18 Bead ID: pdftract-47e42 Related Issue: Inspector URL fragment routing (#page=N for shareable links; back/forward; localStorage)

Summary

Implemented URL fragment routing in the inspector frontend with support for shareable links, browser back/forward navigation, and localStorage persistence.

Changes Made

File: crates/pdftract-cli/src/inspect/frontend/app.js

1. Added URL fragment routing infrastructure (lines 1-19)

  • Added comment header for Phase 7.9.7 URL fragment routing
  • Added isUpdatingFragment flag to prevent double-render on hashchange events

2. Added setupHashChange() function

function setupHashChange(){
  window.addEventListener('hashchange',onHashChange);
}
  • Sets up event listener for browser back/forward button support
  • Called from init() function

3. Added onHashChange() event handler

function onHashChange(){
  // Skip if we're the ones updating the fragment
  if(isUpdatingFragment)return;

  const page=parsePageFromHash();
  if(page===null)return; // Invalid hash, ignore

  // If document not loaded yet, load it first
  if(totalPages===0){
    loadDocument().then(()=>{
      handleHashPage(page);
    });
    return;
  }

  handleHashPage(page);
}
  • Handles hashchange events from browser back/forward buttons
  • Uses isUpdatingFragment flag to prevent double-render when we update the hash programmatically
  • Handles the case where the document hasn't loaded yet

4. Added handleHashPage() function

function handleHashPage(page){
  // Clamp to valid range
  if(page<0){
    console.warn(`Page ${page} is out of range, defaulting to 0`);
    page=0;
  }else if(page>=totalPages){
    console.warn(`Page ${page} is out of range (total pages: ${totalPages}), clamping to ${totalPages-1}`);
    page=totalPages-1;
  }

  // Only load if different from current page
  if(page!==currentPage){
    loadPage(page);
  }
}
  • Clamps out-of-range page numbers with console warnings
  • Avoids unnecessary reloads if already on the target page

5. Added parsePageFromHash() function

function parsePageFromHash(){
  const match=/#page=(\d+)/.exec(location.hash);
  if(!match)return null; // No page in hash

  const page=parseInt(match[1],10);
  if(isNaN(page)){
    console.warn(`Invalid page number in hash: ${match[1]}`);
    return 0; // Default to page 0 for invalid numbers
  }
  if(page<0){
    console.warn(`Negative page number in hash: ${page}`);
    return 0;
  }
  return page;
}
  • Safely parses the page number from URL hash
  • Handles invalid input (NaN, negative numbers) with warnings and defaults

6. Updated updateFragment() function

function updateFragment(){
  // Set flag to prevent hashchange from triggering a page load
  isUpdatingFragment=true;
  history.replaceState(null,'',`#page=${currentPage}`);
  // Use setTimeout to reset the flag after the event loop
  setTimeout(()=>{
    isUpdatingFragment=false;
  },0);
}
  • Uses isUpdatingFragment flag to prevent double-render
  • Resets flag asynchronously after hash update

7. Rewrote loadFragment() function

function loadFragment(){
  // If document metadata is already loaded, handle fragment immediately
  if(totalPages>0){
    const page=parsePageFromHash();
    if(page!==null){
      handleHashPage(page);
    }else{
      // No valid hash, load page 0
      loadPage(0);
    }
  }else{
    // Document not loaded yet, load it then handle fragment
    loadDocument().then(()=>{
      const page=parsePageFromHash();
      if(page!==null){
        handleHashPage(page);
      }else{
        loadPage(0);
      }
    });
  }
}
  • Handles both cases: document already loaded vs. not loaded yet
  • Defaults to page 0 if no valid hash present

8. Fixed thumbnail click handler (lines 665-670)

btn.addEventListener('click',()=>{
  const targetPage=parseInt(btn.dataset.index);
  if(targetPage===currentPage)return;
  loadPage(targetPage);
});
  • Removed manual history.pushState and HashChangeEvent dispatch
  • Now relies on updateFragment() called from loadPage() to update the URL

9. Updated saveLayerState() to handle localStorage errors

function saveLayerState(active){
  try{
    localStorage.setItem(STORAGE_PREFIX+'layers',active.join(','))
  }catch(e){
    // localStorage might be disabled (e.g., privacy mode)
    console.warn('Failed to save layer state to localStorage:',e)
  }
}
  • Gracefully handles localStorage being disabled (e.g., privacy mode)

Acceptance Criteria Status

Criterion Status Notes
URL #page=14 on load → starts on page 14 PASS loadFragment() parses hash and loads the specified page
Navigate via next button → URL updates to #page=15 PASS loadPage() calls updateFragment() which updates the hash
Browser back button → URL goes to #page=14, view updates PASS setupHashChange() sets up hashchange listener that calls handleHashPage()
Bookmark with #page=14 → reopens to page 14 PASS Same as first criterion - hash is parsed on page load
Overlay toggles persist across page refresh PASS Already implemented via loadLayerState()/saveLayerState() using localStorage
Out-of-range #page=999 on 5-page doc → clamps to page 4 PASS handleHashPage() clamps with console warning
Invalid #page=abc → defaults to page 0 PASS parsePageFromHash() handles NaN with warning and defaults to 0

Test Results

To be verified by running the inspector application:

  1. Start the inspector with a multi-page PDF
  2. Navigate via next/prev buttons - URL should update
  3. Use browser back/forward buttons - view should update
  4. Open a URL with #page=N - should start on that page
  5. Test out-of-range page numbers - should clamp with warnings
  6. Test invalid page numbers - should default to page 0
  7. Toggle overlay layers and refresh - state should persist

References

  • Plan section: Phase 7.9.7
  • Coordinator: pdftract-46jjf (parent)
  • Related beads: sidebar nav, keyboard shortcuts